As stated above, I wrote a class that allows you to iterate over a hierarchical set of data, while keeping that hierarchy in order. You do this by specifying one or more root elements (either via an array or a variadic), and a closure that returns the children for a given element.
Since it's implemented as a generic, you can specify an explicit type to use if you know the hierarchy is homogenous, but if not, specify Any
for the type, then in the closure, perform the logic to determine what child type it is.
In addition, the implementation, via recursion, not only returns things in the correct hierarchical order, but it also returns a level so you know how deep the items are. If you don't care about the level, simply append .map{ $0.item }
when initializing the sequence to extract the items directly.
Here's the code for the custom hierarchical sequence...
struct HierarchicalSequence<TItem> : Sequence {
typealias GetChildItemsDelegate = (TItem) -> [TItem]?
init(_ rootItems:TItem..., getChildItems: @escaping GetChildItemsDelegate){
self.init(rootItems, getChildItems: getChildItems)
}
init(rootItems:[TItem], getChildItems: @escaping GetChildItemsDelegate){
self.rootItems = rootItems
self.getChildItems = getChildItems
}
let rootItems : [TItem]
let getChildItems : GetChildItemsDelegate
class Iterator : IteratorProtocol {
typealias Element = (level:Int, item:TItem)
init(level:Int, items:[TItem], getChildItems: @escaping GetChildItemsDelegate){
self.level = level
self.items = items
self.getChildItems = getChildItems
}
let level : Int
let items : [TItem]
let getChildItems : GetChildItemsDelegate
private var nextIndex = 0
var childIterator:Iterator?
func next() -> Element? {
// If there's a child iterator, use it to see if there's a 'next' item
if let childIterator = childIterator {
if let childIteratorResult = childIterator.next(){
return childIteratorResult
}
// No more children so let's clear out the iterator
self.childIterator = nil
}
if nextIndex == items.count {
return nil
}
let item = items[nextIndex]
nextIndex += 1
// Set up the child iterator for the next call to 'next' but still return 'item' from this call
if let childItems = getChildItems(item),
childItems.count > 0 {
childIterator = Iterator(
level : level + 1,
items : childItems,
getChildItems : getChildItems)
}
return (level, item)
}
}
func makeIterator() -> Iterator {
return Iterator(level: 0, items: rootItems, getChildItems: getChildItems)
}
}
Let's see an example of how to use it. First, let's start with some JSON data...
public let jsonString = """
[
{
"name" : "Section A",
"subCategories" : [
{
"name" : "Category A1",
"subCategories" : [
{ "name" : "Component A1a" },
{ "name" : "Component A1b" }
]
},
{
"name" : "Category A2",
"subCategories" : [
{ "name" : "Component A2a" },
{ "name" : "Component A2b" }
]
}
]
},
{
"name" : "Section B",
"subCategories" : [
{
"name" : "Category B1",
"subCategories" : [
{ "name" : "Component B1a" },
{ "name" : "Component B1b" }
]
},
{
"name" : "Category B2",
"subCategories" : [
{ "name" : "Component B2a" },
{ "name" : "Component B2b" }
]
}
]
}
]
"""
Here's the models and code to load that data
class Category : Codable {
let name : String
let subCategories : [Category]?
}
public let jsonData = jsonString.data(using: .utf8)!
var rootCategories = try! JSONDecoder().decode([Category].self, from: jsonData)
Here's how you use the sequence getting all the categories along with their depths...
let allCategoriesWithDepth = HierarchicalSequence(rootItems:rootCategories){ $0.subCategories }
for (depth, category) in allCategoriesWithDepth {
print("\(String(repeating: " ", count: depth * 2))\(depth): \(category.name)")
}
And finally, here's the output...
0: Section A
1: Category A1
2: Component A1a
2: Component A1b
1: Category A2
2: Component A2a
2: Component A2b
0: Section B
1: Category B1
2: Component B1a
2: Component B1b
1: Category B2
2: Component B2a
2: Component B2b
Enjoy!