0

Routinely in my various projects, I have to deal with iterating over hierarchical data. Being as common as it is, it always frustrated me that I had to write so much boilerplate code to do it.

Well thanks to Swifts ability to write custom Sequence classes, I decided to see if I could write one that would achieve this goal in a reusable fashion. Below is my result.

I decided to post this here per Jeff Atwood's [own comments on encouraging posting your own answers][1] where he says...

It is not merely OK to ask and answer your own question, it is explicitly encouraged [...] I do it all the time!

As such, I'm providing this solution here in hopes of helping others when they come to search this site.

Enjoy! :)

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • I'm voting to close this question as off-topic because it belongs on https://codereview.stackexchange.com/ – par Sep 19 '18 at 01:05
  • I can respect your point of view, but the reason why I put it here is because this is the site people go to for information such as this. While yes. I didn’t have a question, that doesn’t mean it’s not information that could be useful/helpful to others. But again, I do understand your perspective. – Mark A. Donohoe Sep 19 '18 at 01:09
  • At the end of the day I'm just one vote, but you're really asking for commentary _on your answer_ ... which *is* off-topic here and will lead to a lot of opinion-based other answers, which is another reason we close questions. I'm not commenting whatsoever on your solution to the problem, it is just my opinion that the delivery is not right for this format. – par Sep 19 '18 at 01:15
  • Well, the feedback is secondary to the intent of the post, which is to show how you can iterate any hierarchical data using this sequence type, which is something I've been looking for for a long time. The feedback is just to see if I missed something or if there's a better way, but as I said, I'm pretty happy with how this turned out. – Mark A. Donohoe Sep 19 '18 at 01:17
  • There is no clear question here, so it's likely this will help very few people. If you self-answer a question please take the time to make the question clear. – Cristik Sep 19 '18 at 05:34

1 Answers1

0

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!

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286