1

I have a collection of LayoutElement that I receive from an API.

For simplicity sake, imagine it contains 2 properties -

String or [LayoutElement]

I'd like to iterate over this collection and if the value has children, loop over the children recursively, returning the LayoutElement that contains a String

Essentially I should end up with a [LayoutElement] that contains only the elements that have a String value.

As this will be used in multiple places, I thought I could create an extension on Array where the element is LayoutElement but I am unsure how best to do this in the case of element.children.

import UIKit


struct LayoutElement {
  let children: [LayoutElement]?
  let text: String?
}


let data = [
  LayoutElement(children: nil, text: "First Text Element"),
  LayoutElement(children: [
    LayoutElement(children: nil, text: "Second Text Element"),
    LayoutElement(children: nil, text: "Third Text Element"),
    LayoutElement(children: [
      LayoutElement(children: nil, text: "Fourth Text Element"),
    ], text: nil)
  ], text: nil),
]



extension Array where Element == LayoutElement {
  var asLayout: [LayoutElement] {
    return map { element in
      if let children = element.children {
        // ???
      } else {
        return element
      }
    }
  }
}

let output = data.asLayout

print(output)

3 Answers 3

2

You couldn't figure how to do it because you chose the wrong higher-order function. You should use flatMap, not map.

map transforms each array element into another element, but flatMap can transform each element into several other elements. You should use flatMap when the resulting array has a different size than the starting array, which is the case here. data has 2 elements, while output should have 4 elements. map can never increase the number of elements.

  var asLayout: [LayoutElement] {
    return flatMap { element -> [LayoutElement] in
      // for each element, we return the array of elements into which we want to transform it
      if let children = element.children {
        return children.asLayout
      } else {
        return [element] // we don't want to transform it, so we return an array containing only "element"
      }
    }
  }

Returning children.asLayout should do the job.

Or in one line:

var asLayout: [LayoutElement] {
    flatMap { $0.children?.asLayout ?? [$0] }
}
Sign up to request clarification or add additional context in comments.

Comments

1

You should be able to achieve this by capturing your collection in a local array before returning the result

extension Array where Element == LayoutElement {
  var asLayout: [LayoutElement] {
    var flatten: [LayoutElement] = []
    forEach { item in
      if let children = item.children {
        children.asLayout.forEach { child in
          flatten.append(child)
        }
      } else {
        flatten.append(item)
      }
    }

    return flatten
  }
}

Comments

1

Have a temporary array to collect all the results:

extension Array where Element == LayoutElement {
    var asLayout: [LayoutElement] {
        var result = [LayoutElement]()
        forEach {
            if let children = $0.children {
                result.append(contentsOf: children.asLayout)
            } else if $0.text != nil {
                result.append($0)
            }
        }
        return result
    }
}

1 Comment

+1 for the use of ` result.append(contentsOf: children.asLayout)` - that completely slipped my mind, it makes the syntax much sleeker

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.