List comprehension in Swift

2019-01-16 06:43发布

The language guide has revealed no trace of list comprehension. What's the neatest way of accomplishing this in Swift? I'm looking for something similar to:

evens = [ x for x in range(10) if x % 2 == 0]

7条回答
太酷不给撩
2楼-- · 2019-01-16 07:05

Generally, a list comprehension in Python can be written in the form:

[f(x) for x in xs if g(x)]

Which is the same as

map(f, filter(g, xs))

Therefore, in Swift you can write it as

listComprehension<Y>(xs: [X], f: X -> Y, g: X -> Bool) = map(filter(xs, g), f)

For example:

map(filter(0..<10, { $0 % 2 == 0 }), { $0 })
查看更多
The star\"
3楼-- · 2019-01-16 07:12

As of Swift 2 you can do something like this:

var evens = [Int]()
for x in 1..<10 where x % 2 == 0 {
    evens.append(x)
}

// or directly filtering Range due to default implementations in protocols (now a method)
let evens = (0..<10).filter{ $0 % 2 == 0 }
查看更多
你好瞎i
4楼-- · 2019-01-16 07:12

Got to admit, I am surprised nobody mentioned flatmap, since I think it's the closest thing Swift has to list (or set or dict) comprehension.

var evens = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].flatMap({num -> Int? in 
    if num % 2 == 0 {return num} else {return nil}
})

Flatmap takes a closure, and you can either return individual values (in which case it will return an array with all of the non-nil values and discard the nils) or return array segments (in which case it will catenate all of your segments together and return that.)

Flatmap seems mostly (always?) to be unable to infer return values. Certainly, in this case it can't, so I specify it as -> Int? so that I can return nils, and thus discard the odd elements.

You can nest flatmaps if you like. And I find them much more intuitive (although obviously also a bit more limited) than the combination of map and filter. For example, the top answer's 'evens squared', using flatmap, becomes,

var esquares = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].flatMap({num -> Int? in 
    if num % 2 == 0 {return num * num} else {return nil}
})

The syntax isn't quite as one-line not-quite-the-same-as-everything-else as python's is. I'm not sure if I like that less (because for the simple cases in python it's very short and still very readable) or more (because complex cases can get wildly out of control, and experienced python programmers often think that they're perfectly readable and maintainable when a beginner at the same company can take half an hour to puzzle out what it was intended to do, let alone what it's actually doing.)

Here is the version of flatMap from which you return single items or nil, and here is the version from which you return segments.

It's probably also worth looking over both array.map and array.forEach, because both of them are also quite handy.

查看更多
该账号已被封号
5楼-- · 2019-01-16 07:12

One aspect of list comprehension that hasn't been mentioned in this thread is the fact that you can apply it to multiple lists' Cartesian product. Example in Python:

[x + y for x in range(1,6) for y in range(3, 6) if x % 2 == 0]

… or Haskell:

[x+y | x <- [1..5], y <- [3..5], x `mod` 2 == 0]

In Swift, the 2-list equivalent logic is

list0
    .map { e0 in
        list1.map { e1 in
            (e0, e1)
        }
    }
.joined()
.filter(f)
.map(g)

And we'd have to increase the nesting level as the number of lists in input increases.

I recently made a small library to solve this problem (if you consider it a problem). Following my first example, with the library we get

Array(1...5, 3...5, where: { n, _ in n % 2 == 0}) { $0 + $1 }

The rationale (and more about list comprehension in general) is explained in an blog post.

查看更多
对你真心纯属浪费
6楼-- · 2019-01-16 07:14

One way would be :

var evens: Int[]()
for x in 0..<10 {
    if x%2 == 0 {evens += x} // or evens.append(x)
}
查看更多
啃猪蹄的小仙女
7楼-- · 2019-01-16 07:20

With Swift 3, according to your needs or tastes, you may choose one of the seven following Playground codes that are kind of equivalent to Python list comprehension.


#1. Using stride(from:to:by:) function

let sequence = stride(from: 0, to: 10, by: 2)
let evens = Array(sequence)
// let evens = sequence.map({ $0 }) // also works
print(evens) // prints [0, 2, 4, 6, 8]

#2. Using CountableRange filter(_:) method

let range = 0 ..< 10
let evens = range.filter({ $0 % 2 == 0 })
print(evens) // prints [0, 2, 4, 6, 8]

#3. Using CountableRange flatMap(_:) method

let range = 0 ..< 10
let evens = range.flatMap({ $0 % 2 == 0 ? $0 : nil })
print(evens) // prints [0, 2, 4, 6, 8]

#4. Using sequence(first:next:) function

let unfoldSequence = sequence(first: 0, next: {
    $0 + 2 < 10 ? $0 + 2 : nil
})
let evens = Array(unfoldSequence)
// let evens = unfoldSequence.map({ $0 }) // also works
print(evens) // prints [0, 2, 4, 6, 8]

#5. Using AnySequence init(_:) initializer

let anySequence = AnySequence<Int>({ () -> AnyIterator<Int> in
    var value = 0
    return AnyIterator<Int> {
        defer { value += 2 }
        return value < 10 ? value : nil
    }
})
let evens = Array(anySequence)
// let evens = anySequence.map({ $0 }) // also works
print(evens) // prints [0, 2, 4, 6, 8]

#6. Using for loop with where clause

var evens = [Int]()
for value in 0 ..< 10 where value % 2 == 0 {
    evens.append(value)
}
print(evens) // prints [0, 2, 4, 6, 8]

#7. Using for loop with if condition

var evens = [Int]()
for value in 0 ..< 10 {
    if value % 2 == 0 {
        evens.append(value)
    }
}
print(evens) // prints [0, 2, 4, 6, 8]
查看更多
登录 后发表回答