在Swift中列出理解

时间:2014-06-02 21:28:09

标签: swift list-comprehension

语言指南没有透露列表理解的痕迹。 在Swift中实现这一目标的最佳方式是什么?我正在寻找类似的东西:

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

8 个答案:

答案 0 :(得分:57)

从Swift 2.x开始,你的Python风格列表理解有一些简短的等价物。

Python公式的最简单的改编(读取类似“将变换应用于受过滤器影响的序列”)涉及链接所有map可用的filterSequenceType方法} s,从Range开始:

// Python: [ x for x in range(10) if x % 2 == 0 ]
let evens = (0..<10).filter { $0 % 2 == 0 }

// Another example, since the first with 'x for x' doesn't
// use the full ability of a list comprehension:
// Python: [ x*x for x in range(10) if x % 2 == 0 ]
let evenSquared = (0..<10).filter({ $0 % 2 == 0 }).map({ $0 * $0 })

请注意,Range是抽象的 - 它实际上并不创建您要求的整个值列表,只是一个懒惰地按需提供它们的构造。 (从这个意义上来说,它更像是Python的xrange。)但是,filter调用会返回Array,因此您将失去“懒惰”方面。如果你想让这个集合一直保持懒惰,那就这样说:

// Python: [ x for x in range(10) if x % 2 == 0 ]
let evens = (0..<10).lazy.filter { $0 % 2 == 0 }
// Python: [ x*x for x in range(10) if x % 2 == 0 ]
let evenSquared = (0..<10).lazy.filter({ $0 % 2 == 0 }).map({ $0 * $0 })

与Python中的列表推导语法(以及其他一些语言中的类似构造)不同,Swift中的这些操作遵循与其他操作相同的语法。也就是说,构造,过滤和操作一系列数字的语法风格与过滤和操作对象数组一样 - 您不必使用函数/方法语法进行某种工作并列出另一个的理解语法。

您可以将其他功能传递到filtermap来电,并链接其他方便的变换,例如sortreduce

// func isAwesome(person: Person) -> Bool
// let people: [Person]
let names = people.filter(isAwesome).sort(<).map({ $0.name })

let sum = (0..<10).reduce(0, combine: +)

根据您的目标,可能会有更简洁的方式来表达您的意思。例如,如果您特别需要偶数整数列表,则可以使用stride

let evenStride = 0.stride(to: 10, by: 2) // or stride(through:by:), to include 10

与范围一样,这会为您提供一个生成器,因此您需要从中生成Array或者遍历它以查看所有值:

let evensArray = Array(evenStride) // [0, 2, 4, 6, 8]

编辑:对Swift 2.x进行了重大修订。如果您想要Swift 1.x,请参阅编辑历史记录。

答案 1 :(得分:6)

使用Swift 5,您可以选择其中一个七个以下的Playground示例代码来解决您的问题。

#1。使用stride(from:to:by:)函数

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。使用Range filter(_:)方法

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

#3。使用Range compactMap(_:)方法

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

#4。使用sequence(first:next:)函数

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。使用AnySequence init(_:)初始值设定项

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。使用for子句

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

#7。使用带有if条件的for循环

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

答案 2 :(得分:3)

通常,Python中的列表理解可以用以下形式编写:

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

相同
map(f, filter(g, xs))

因此,在Swift中你可以把它写成

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

例如:

map(filter(0..<10, { $0 % 2 == 0 }), { $0 })

答案 3 :(得分:3)

从Swift 2开始,你可以这样做:

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 }

答案 4 :(得分:1)

不得不承认,我很惊讶没有人提到flatmap,因为我认为这是Swift必须列出(或设置或理解)理解的最接近的东西。

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采用闭包,你可以返回单个值(在这种情况下,它将返回一个包含所有非零值的数组并丢弃nils)或返回数组段(在这种情况下,它将连接所有的你的片段在一起并返回。)

Flatmap似乎大多数(总是?)无法推断返回值。当然,在这种情况下它不能,所以我将其指定为 - &gt;诠释?所以我可以返回nils,从而丢弃奇数元素。

如果您愿意,可以嵌套平面地图。而且我发现它们比地图和过滤器的组合更直观(尽管显然也更有限)。例如,使用flatmap的最佳答案'均匀'变为,

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}
})

语法并不像python那样与单行不完全相同。我不确定我是否喜欢那么少(因为对于python中的简单情况,它很短且仍然非常易读)或更多(因为复杂的情况可能会失控,经验丰富的python程序员经常认为它们是完美的当同一家公司的初学者花费半个小时来解决它的目的时,可读性和可维护性,更不用说它实际上在做什么了。)

Here是flatMap的版本,您可以从中返回单个项目或nil,而here是您返回段的版本。

同样值得查看array.map和array.forEach,因为它们都非常方便。

答案 5 :(得分:0)

一种方法是:

var evens: Int[]()
for x in 0..<10 {
    if x%2 == 0 {evens += x} // or evens.append(x)
}

答案 6 :(得分:0)

此主题中未提及的列表理解的一个方面是您可以将其应用于多个列表的笛卡尔积。 Python中的示例:

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

...或者Haskell:

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

在Swift中,2列表等效逻辑是

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

随着输入列表数量的增加,我们必须增加嵌套级别。

我最近做了一个小library来解决这个问题(如果你认为这是一个问题)。按照我的第一个例子,我们得到了库

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

blog post解释了基本原理(以及更多关于列表理解的内容)。

答案 7 :(得分:0)

这是Array类型的扩展,它将filtermap组合成一个方法:

extension Array {

    func filterMap(_ closure: (Element) -> Element?) -> [Element] {

        var newArray: [Element] = []
        for item in self {
            if let result = closure(item) {
                newArray.append(result)
            }
        }
        return newArray
    }

}

它与map相似,只不过您可以返回nil表示您不希望将元素添加到新数组中。例如:

let items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let newItems = items.filterMap { item in
    if item < 5 {
        return item * 2
    }
    return nil
}

这也可以写得更简洁:

let newItems = items.filterMap { $0 < 5 ? $0 * 2 : nil }

在这两个示例中,如果元素小于5,则将其乘以2并添加到新数组中。如果闭包返回nil,则不添加元素。因此,newIems[2, 4, 6, 8]

以下是与Python等效的内容:

items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
newItems = [n * 2 for n in items if n < 5]