user1107173
user1107173

Reputation: 10764

Swift: Avoid imperative For Loop

What I'm trying to accomplish in imperative:

var mapNames = [String]()
var mapLocation = [String]()

for valueMap in valueMaps {
    if let name = valueMap.name {
         mapNames.append(name)
    }
    if let location = valueMap.location {
        mapLocation.append(location)
    }
}

What's the best way using a high order function or perhaps an array method (array.filter etc.) to compact the code above and also avoid using the for loop

Here is what I have tried, but the compiler gives an error:

let getArrayOfNames = valueMaps.filter() {
    if let name = ($0 as valueMaps).name as [String]! {
        return name;
    }
}

let getArrayOfLocations = valueMaps.filter() {
    if let type = ($0 as valueMaps).location as [String]! {
        return type;
    }
}

Upvotes: 1

Views: 791

Answers (2)

Martin R
Martin R

Reputation: 539955

You need both filter() and map() :

let mapNames = valueMaps.filter( {$0.name != nil }).map( { $0.name! })
let mapLocations = valueMaps.filter( {$0.location != nil }).map( { $0.location! })

The filter takes a predicate as an argument (which specifies which elements should be included in the result), and the map takes a transformation as an argument. You were trying to merge both aspects into the filter, which is not possible.

Update: As of Swift 2(?) has a flatMap() method for sequences, which can be used to obtain the result in a single step:

let mapNames = valueMaps.flatMap { $0.name }

The closure is applied to all array elements, and the return value is an array with all non-nil unwrapped results.

Upvotes: 5

oisdk
oisdk

Reputation: 10091

The filter() function needs its closure to return a bool - not the value you want to store in an array. You could chain filter and map together to get what you want, then:

let getArrayOfNames = valueMaps
    .filter { $0.name != nil }
    .map{ $0.name! }

Or, to do it in one function, with reduce:

let getArrayOfNames = valueMaps
    .reduce([String]()) {
        accu, element in
        if let name = element.name {
            return accu + [name]
        } else {
            return accu
        }
}

Actually, the reduce can be a little better:

let getArrayOfNames = valueMaps.reduce([String]()) {
  (names, value) in names + (value.name.map{[$0]} ?? [])
}

let getArrayOfLocations = valueMaps.reduce([String]()) {
  (locs, value) in locs + (value.location.map{[$0]} ?? [])
}

Upvotes: 2

Related Questions