Reputation: 3
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
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