How to filter 3 dimension array of object in swift

So I was supposed to filter the 3D array of objects based on the object name that contains a text by user input. The output should be the 1 flat array and the parent array should be included as well, The matching object should be at any level of depth.

here is the data model:

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 now, I was come up with something like this, but it reduces the performance of my app. cause it's using for loop I'm guessing

    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)
            }
        }
    }

The result should be like this:

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),
]

Upvotes: 0

Views: 322

Answers (1)

Joakim Danielson
Joakim Danielson

Reputation: 51973

Here is a solution where I make the filtering simpler by first converting the data structure into a dictionary, this is made with the assumption that id is the unique identifier for Category

This function creates the dictionary

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
}

and then we use it to filter

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

Then to generate the expected output (although perhaps not in the right order) we make use of the fact that both the parent and root Category is given for a 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)
    }
}

If the order of the elements in the array is important that shouldn't be too hard to fix.

Upvotes: 1

Related Questions