Closures to flat nested objects?

2019-01-28 17:11发布

问题:

I'm starting to learn about closures and want to implement them in a project I'm working on and I'd like some help.

I have a class defined as follows:

class MyObject {
var name: String?
var type: String?
var subObjects: [MyObject]?
}

And I want to use closures or higher oder functions (something like flatMap comes to mind) to flatten an [MyObject] and joining all MyObject and subOjects into one array.

I've tried using [MyObject].flatMap() but this operation doesn't return the nested subObjects.

回答1:

One approach to flattening a recursive class structure is with a recursive function.

Here is the class that we would like flattened:

public class Nested {
    public let n : Int
    public let sub : [Nested]?
    public init(_ n:Int, _ sub:[Nested]?) {
        self.n = n
        self.sub = sub
    }
}

Here is the function that demonstrates how this could be done:

func test() {
    let h = [
        Nested(1, [Nested(2, nil), Nested(3, nil)])
    ,   Nested(4, nil)
    ,   Nested(5, [Nested(6, nil), Nested(7, [Nested(8, nil), Nested(9, nil)])])
    ]
    func recursiveFlat(next:Nested) -> [Nested] {
        var res = [Nested]()
        res.append(next)
        if let subArray = next.sub {
            res.appendContentsOf(subArray.flatMap({ (item) -> [Nested] in
                recursiveFlat(item)
            }))
        }
        return res
    }
    for item in h.flatMap(recursiveFlat) {
        print(item.n)
    }
}

The heart of this approach is recursiveFlat local function. It appends the content of the nested object to the result, and then conditionally calls itself for each element to add their contents as well.



回答2:

First, I would highly recommend making the type of subObjects be non-optional. There's rarely a reason for optional arrays. Do you really need to distinguish between "no array" and "an empty array?" This is very uncommon. If you make subObjects just be an array, you can write what you're describing as a simple recursive function:

func flattenMyObjects(myObjects: [MyObject]) -> [MyObject] {
    return myObjects.flatMap { (myObject) -> [MyObject] in
        var result = [myObject]
        result.appendContentsOf(flattenMyObjects(myObject.subObjects))
        return result
    }
}

If you need it to be optional, the changes are minor (you'll need to add an if-let or something similar).