How to expand array elements in Swift? (i.e. Array <Int?> as an array <Int>)
Let's say that I have an array of strings and I want to map it to an Int array. I can use the map function:
var arrayOfStrings: Array = ["0", "a"] let numbersOptional = arrayOfStrings.map { $0.toInt() } // numbersOptional = "[Optional(0), nil]" The numbers are now an Int? Array, but I need an Int array. I know I can do this:
let numbers = arrayOfStrings.map { $0.toInt() }.filter { $0 != nil }.map { $0! } // numbers = [0] But it does not seem very fast. Convert from Int array? to Array of Int requires calling both a filter and a map with a fairly large number of templates. Is there a faster way to do this?
Update:. Compared to Swift 1.2, there is a built-in flatMap method for arrays, but it does not accept Optional s, so the helper below is still useful.
I like to use the flatMap helper function for things like the Scala flatMap method for collections (which can consider Scala Option as a collection of 0 or 1 elements, roughly):
func flatMap<C : CollectionType, T>(source: C, transform: (C.Generator.Element) -> T?) -> [T] { var buffer = [T]() for elem in source { if let mappedElem = transform(elem) { buffer.append(mappedElem) } } return buffer } let a = ["0", "a", "42"] let b0 = map(a, { $0.toInt() }) // [Int?] - [{Some 0}, nil, {Some 42}] let b1 = flatMap(a, { $0.toInt() }) // [Int] - [0, 42] This definition of flatMap is rather a special case for Optional of what a more general flatMap should do:
func flatMap<C : CollectionType, T : CollectionType>(source: C, transform: (C.Generator.Element) -> T) -> [T.Generator.Element] { var buffer = [T.Generator.Element]() for elem in source { buffer.extend(transform(elem)) } return buffer } where do we get
let b2 = flatMap(a, { [$0, $0, $0] }) // [String] - ["0", "0", "0", "a", "a", "a", "42", "42", "42"] Using reduce to create a new array may be more idiomatic
func filterInt(a: Array<String>) -> Array<Int> { return a.reduce(Array<Int>()) { var a = $0 if let x = $1.toInt() { a.append(x) } return a } } Example
filterInt(["0", "a", "42"]) // [0, 42] What you really need is the collect ( map + filter ) method. Given the specific filter you need to apply, even flatMap will work in this case (see Jean-Philippe's answer). Too bad both methods are not provided by the fast library.
Theres no good built-in standard Swift library method. However, Haskell has a mapMaybe function that does what you are looking for (assuming you just want to distinguish between null values). Heres the equivalent in Swift:
func mapSome<S: SequenceType, D: ExtensibleCollectionType> (source: S, transform: (S.Generator.Element)->D.Generator.Element?) -> D { var result = D() for x in source { if let y = transform(x) { result.append(y) } } return result } // version that defaults to returning an array if unspecified func mapSome<S: SequenceType, T> (source: S, transform: (S.Generator.Element)->T?) -> [T] { return mapSome(source, transform) } let s = ["1","2","elephant"] mapSome(s) { $0.toInt() } // [1,2]