Как отфильтровать трехмерный массив объектов в Swift

avatar
Darindra Ramantya Khadifa
8 августа 2021 в 15:39
108
1
0

Итак, я должен был отфильтровать трехмерный массив объектов на основе имени объекта, содержащего текст, введенный пользователем. Выход должен быть плоским массивом 1, и родительский массив также должен быть включен. Соответствующий объект должен быть на любом уровне глубины.

вот модель данных:

let data = [
    Category(
        id: "1",
        name: "Stationary",
        iconImageUrl: nil,
        parent: nil,
        tree: 1,
        rootId: 1,
        child: [
            Category(
                id: "2",
                name: "Office Stationary",
                iconImageUrl: nil,
                parent: 1,
                tree: 2,
                rootId: 1,
                child: [
                    Category(id: "3", name: "Pen", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                    Category(id: "4", name: "Pencil", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                    Category(id: "5", name: "Ruler", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                    Category(id: "6", name: "Paper", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil)
                ],
                rgbColor: nil
            ),
            Category(
                id: "7",
                name: "Home Stationary",
                iconImageUrl: nil,
                parent: 1,
                tree: 2,
                rootId: 1,
                child: [
                    Category(id: "8", name: "Telephone", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                    Category(id: "9", name: "Fax", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                    Category(id: "10", name: "Komputer", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                    Category(id: "11", name: "Laptop", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil)
                ],
                rgbColor: nil
            )
        ],
        rgbColor: nil
    )
]

на данный момент я придумал нечто подобное, но это снижает производительность моего приложения. потому что он использует цикл for, я думаю

    var searchText = "pen"
    var filteredCategories: [Category] = []
    // Loop 1
    for parent in data.value {
        if let child = parent.child {
            // Loop 2
            for firstChild in child {
                if let child = firstChild.child {
                    // Loop 3
                    for secondChild in child {
                        // Check if the last array contains search text
                        if secondChild.name!.range(of: searchText, options: .caseInsensitive) != nil {
                            // Check if the result has already 1st array so it wont duplicates
                            if !filteredCategories.contains(where: { $0.name == parent.name }) {
                                filteredCategories.append(parent)
                            }
                                // Check if the result has already contain 2nd array so it wont duplicates
                            if !filteredCategories.contains(where: { $0.name == firstChild.name }) {
                                filteredCategories.append(firstChild)
                            }
                            filteredCategories.append(secondChild)
                        }
                    }
                }
                if firstChild.name!.range(of: searchText, options: .caseInsensitive) != nil {
                    if !filteredCategories.contains(where: { $0.name == parent.name }) {
                        filteredCategories.append(parent)
                    }
                    if !filteredCategories.contains(where: { $0.name == firstChild.name }) {
                        filteredCategories.append(firstChild)
                    }
                }
            }
        }
        if parent.name!.range(of: searchText, options: .caseInsensitive) != nil {
            if !filteredCategories.contains(where: { $0.name == parent.name }) {
                filteredCategories.append(parent)
            }
        }
    }

Результат должен быть таким:

let result = [
    Category(
        id: "1",
        name: "Stationary",
        iconImageUrl: nil,
        parent: nil,
        tree: 1,
        rootId: 1,
        child: [
            Category(
                id: "2",
                name: "Office Stationary",
                iconImageUrl: nil,
                parent: 1,
                tree: 2,
                rootId: 1,
                child: [
                        Category(id: "3", name: "Pen", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                        Category(id: "4", name: "Pencil", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                        Category(id: "5", name: "Ruler", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                        Category(id: "6", name: "Paper", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil)
                ],
                rgbColor: nil
            ),
        ],
        rgbColor: nil),
    Category(
        id: "2",
        name: "Office Stationary",
        iconImageUrl: nil,
        parent: 1,
        tree: 2,
        rootId: 1,
        child: [
                Category(id: "3", name: "Pen", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                Category(id: "4", name: "Pencil", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                Category(id: "5", name: "Ruler", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
                Category(id: "6", name: "Paper", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil)
        ],
        rgbColor: nil
    ),
    Category(id: "3", name: "Pen", iconImageUrl: nil, parent: 2, tree: 3, rootId: 1, child: nil, rgbColor: nil),
]
Источник
matt
8 августа 2021 в 15:43
1

Не показывать и не ссылаться на изображения кода. Это текст. Скопируйте и вставьте модель данных в вопрос.

matt
8 августа 2021 в 15:45
1

Также, пожалуйста, попробуйте уточнить, в чем вопрос. Приведите пример ввода, желаемого результата и того, что идет не так. Спасибо!

Darindra Ramantya Khadifa
8 августа 2021 в 16:09
0

привет, Мэтт, спасибо за вклад, извините за ошибку, я уже обновил свой вопрос, надеюсь, он был достаточно ясен

matt
8 августа 2021 в 16:15
0

Хорошо, спасибо. Но я не понимаю логики, по которой ввод должен генерировать вывод. Можете ли вы объяснить словами, в чем заключается цель?

Darindra Ramantya Khadifa
8 августа 2021 в 16:21
0

Итак, мне дали текст для поиска, и я должен был отфильтровать 3D-массив по их имени на основе этого текста. Выход должен быть 1 плоским массивом, и родительский массив также должен быть включен

matt
8 августа 2021 в 16:24
0

Итак, в примере первым шагом будет поиск «ручки» в качестве имени (или в названии) категории на любой глубине? Или заранее известно, что нам нужно смотреть только на глубину 3?

Darindra Ramantya Khadifa
8 августа 2021 в 16:29
0

да, это будет поиск имени объекта, содержащего «перо» на любой глубине, но этот родительский объект также должен быть включен

matt
8 августа 2021 в 16:38
0

Известно ли, что существует только одна такая категория? То есть, когда я найду категорию "ручка", я могу остановиться?

Darindra Ramantya Khadifa
8 августа 2021 в 16:43
0

нет, это может быть много категорий, а также если имя объекта содержит «ручка», но не должно быть повторяющихся категорий.

Darindra Ramantya Khadifa
8 августа 2021 в 16:48
0

К сожалению, мы не смогли переопределить категорию, поскольку модель категории получена из API JSON OBJECT.

Joakim Danielson
8 августа 2021 в 17:02
0

Не по теме, но зачем позволять внешнему API решать, как вы проектируете свои объекты модели? Почему бы не создать дизайн, который подходит вам лучше всего, и написать код преобразования, чтобы сопоставить дизайн API и ваш дизайн? (Отвечать не обязательно, это скорее риторический вопрос)

Alexander
9 августа 2021 в 15:40
0

Этот код можно исправить с помощью функций более высокого порядка, но в будущем, если вы столкнетесь со случаями, когда необходимы такие большие циклы, ваш код получит большую пользу от guard и раннего возврата вместо глубоко вложенных операторов if, таких как это

Ответы (1)

avatar
Joakim Danielson
8 августа 2021 в 17:50
1

Вот решение, в котором я упрощаю фильтрацию, сначала преобразовывая структуру данных в словарь, это сделано с предположением, что id является уникальным идентификатором для Category

Эта функция создает словарь

func flatten(categories: [Category]) -> [String: Category] {
    var result = [String: Category]()
    for category in categories {
        result[category.id] = category
        if let children = category.child {
            let temp = flatten(categories: children)
            result.merge(temp, uniquingKeysWith: { (cat1, _) in return cat1 })
        }
    }

    return result
}

и затем мы используем его для фильтрации

let all = flatten(categories: data)
let found = all.mapValues(\.name).filter { $0.value == "Pen" }

Затем, чтобы сгенерировать ожидаемый результат (хотя, возможно, не в правильном порядке), мы используем тот факт, что и родитель, и корень Category заданы для Category

.
var result = [Category]()
for key in found.keys {
    guard let category = all[key] else { continue }
    result.append(category)

    if let parentId = category.parent, let parent = all["\(parentId)"] {
        result.append(parent)
    }

    if let root = all["\(category.rootId)"] {
        result.append(root)
    }
}

Если порядок элементов в массиве важен, исправить это не составит труда.

Joakim Danielson
9 августа 2021 в 06:05
0

@DarindraRamantyaKhadifa, но это не должно быть сложно исправить самостоятельно, я ответил на ваш вопрос о том, как фильтровать и создавать массив результатов, включая родителей, поэтому, пожалуйста, уважайте это. Кроме того, в вопросе ничего не упоминается о том, как сортировать, поэтому я не знаю, как это сделать.

Alexander
9 августа 2021 в 15:43
0

Этот flatten выглядит как рекурсивная операция "key by". Вы можете использовать Dictionary.init(_:uniquingKeysWith:) для этого случая вместо ручного цикла for