Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unwrap the elements of an Array in Swift? (ie. Array<Int?> as Array<Int>)

Tags:

ios

swift

Lets say I have an Array of String and I want to map it to an Array of Int I can use the map function:

var arrayOfStrings: Array = ["0", "a"]
let numbersOptional = arrayOfStrings.map { $0.toInt() }
// numbersOptional = "[Optional(0), nil]"

Numbers is now an Array of Int?, but I want an Array of Int. I know I can do this:

let numbers = arrayOfStrings.map { $0.toInt() }.filter { $0 != nil }.map { $0! }
// numbers = [0]

But that doesn't seem very swift. Converting from the Array of Int? to Array of Int requires calling both filter and map with pretty much boilerplate stuff. Is there a more swift way to do this?

like image 777
Richard Venable Avatar asked Aug 06 '14 15:08

Richard Venable


2 Answers

Update: As of Swift 1.2, there's a built-in flatMap method for arrays, but it doesn't accept Optionals, so the helper below is still useful.


I like using a helper flatMap function for that sort of things, just like Scala's flatMap method on collections (which can consider an Scala Option as a collection of either 0 or 1 element, roughly spoken):

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 we'd get

let b2 = flatMap(a, { [$0, $0, $0] }) // [String] - ["0", "0", "0", "a", "a", "a", "42", "42", "42"]
like image 138
Jean-Philippe Pellet Avatar answered Sep 29 '22 15:09

Jean-Philippe Pellet


Using reduce to build the new array might 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 would really want is a collect (map + filter) method. Given the specific filter you need to apply, in this case even a flatMap would work (see Jean-Philippe's answer). Too bad both methods are not provided by the swift standard library.

like image 20
Gabriele Petronella Avatar answered Sep 29 '22 14:09

Gabriele Petronella