Как использовать swift flatMap для фильтрации опций из массива
Я немного смущен вокруг flatMap (добавлен в Swift 1.2)
Скажем, у меня есть массив некоторого необязательного типа, например.
let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
В Swift 1.1 я бы сделал фильтр, а затем следующую карту:
let filtermap = possibles.filter({ return $0 != nil }).map({ return $0! })
// filtermap = [1, 2, 3, 4, 5]
Я пытаюсь сделать это с помощью flatMap несколькими способами:
var flatmap1 = possibles.flatMap({
return $0 == nil ? [] : [$0!]
})
и
var flatmap2:[Int] = possibles.flatMap({
if let exercise = $0 { return [exercise] }
return []
})
Я предпочитаю последний подход (потому что мне не нужно делать принудительное разворачивание $0!
... Я боюсь за это и избегаю их любой ценой), за исключением того, что мне нужно указать тип массива.
Есть ли альтернатива, которая определяет тип по контексту, но не имеет принудительного разворота?
Ответы
Ответ 1
С Swift 2 b1 вы можете просто сделать
let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.flatMap { $0 }
Для более ранних версий вы можете закрепить это со следующим расширением:
extension Array {
func flatMap<U>(transform: Element -> U?) -> [U] {
var result = [U]()
result.reserveCapacity(self.count)
for item in map(transform) {
if let item = item {
result.append(item)
}
}
return result
}
}
Одно предостережение (что также верно для Swift 2) состоит в том, что вам может потребоваться явно ввести возвращаемое значение преобразования:
let actuals = ["a", "1"].flatMap { str -> Int? in
if let int = str.toInt() {
return int
} else {
return nil
}
}
assert(actuals == [1])
Для получения дополнительной информации см. http://airspeedvelocity.net/2015/07/23/changes-to-the-swift-standard-library-in-2-0-betas-2-5/
Ответ 2
Мне по-прежнему нравится первое решение, которое создает только один промежуточный
массив. Он может несколько более компактно записываться как
let filtermap = possibles.filter({ $0 != nil }).map({ $0! })
Но flatMap()
без аннотации типа и без принудительного
возможна развертка:
var flatmap3 = possibles.flatMap {
flatMap($0, { [$0] }) ?? []
}
Внешний flatMap
- это метод массива
func flatMap<U>(transform: @noescape (T) -> [U]) -> [U]
а внутренняя flatMap
- это функция
func flatMap<T, U>(x: T?, f: @noescape (T) -> U?) -> U?
Вот простое сравнение производительности (скомпилировано в режиме Release).
Это показывает, что первый метод быстрее, примерно в разном
из 10:
let count = 1000000
let possibles : [Int?] = map(0 ..< count) { $0 % 2 == 0 ? $0 : nil }
let s1 = NSDate()
let result1 = possibles.filter({ $0 != nil }).map({ $0! })
let e1 = NSDate()
println(e1.timeIntervalSinceDate(s1))
// 0.0169369578361511
let s2 = NSDate()
var result2 = possibles.flatMap {
flatMap($0, { [$0] }) ?? []
}
let e2 = NSDate()
println(e2.timeIntervalSinceDate(s2))
// 0.117663979530334
Ответ 3
Вы можете использовать reduce
:
let flattened = possibles.reduce([Int]()) {
if let x = $1 { return $0 + [x] } else { return $0 }
}
Вы все еще вроде объявляете тип, но немного менее навязчивым.
Ответ 4
Поскольку это то, что я, похоже, в конечном итоге делаю довольно много, я изучаю универсальную функцию, чтобы сделать это.
Я попытался добавить расширение к массиву, чтобы я мог сделать что-то вроде possibles.unwraped
, но не мог понять, как сделать расширение в массиве. Вместо этого использовался пользовательский оператор - самая сложная часть здесь пыталась выяснить, какой оператор выбрать. В конце я выбрал >!
, чтобы показать, что массив фильтруется >
, а затем развернут !
.
let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
postfix operator >! {}
postfix func >! <T>(array: Array<T?>) -> Array<T> {
return array.filter({ $0 != nil }).map({ $0! })
}
possibles>!
// [1, 2, 3, 4, 5]