Как использовать swift flatMap для фильтрации optionals из массива
Я немного запутался вокруг flatMap (добавлен в Swift 1.2)
скажем, у меня есть массив некоторого необязательного типа, например
let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
в Swift 1.1 я бы сделал фильтр, а затем такую карту:
let filtermap = possibles.filter({ return != nil }).map({ return ! })
// filtermap = [1, 2, 3, 4, 5]
Я пытался сделать это с помощью flatMap несколькими способами:
var flatmap1 = possibles.flatMap({
return == nil ? [] : [!]
})
и
var flatmap2:[Int] = possibles.flatMap({
if let exercise = { return [exercise] }
return []
})
Я предпочитаю последний подход (потому что мне не нужно делать принудительное разворачивание !
... Я боюсь за них и избегаю их любой ценой.) за исключением того, что мне нужно указать тип массива.
есть ли альтернатива, которая определяет тип по контексту, но не имеет принудительного разворачивания?
5 ответов
С Swift 2 b1 вы можете просто сделать
let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.flatMap { }
для более ранних версий, вы можете оболочек с следующим расширением:
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/
мне все еще нравится первое решение, которое создает только одно промежуточное матрица. Его можно немного более компактно записать как
let filtermap = possibles.filter({ != nil }).map({ ! })
но flatMap()
без аннотации типа и без принудительной
возможна распаковка:
var flatmap3 = possibles.flatMap {
flatMap(, { [] }) ?? []
}
внешний flatMap
метод выбора
func flatMap<U>(transform: @noescape (T) -> [U]) -> [U]
и внутри flatMap
функция
func flatMap<T, U>(x: T?, f: @noescape (T) -> U?) -> U?
вот простое сравнение производительности (скомпилировано в режиме выпуска). Это показывает, что первый метод быстрее, примерно в два раза из 10:
let count = 1000000
let possibles : [Int?] = map(0 ..< count) { % 2 == 0 ? : nil }
let s1 = NSDate()
let result1 = possibles.filter({ != nil }).map({ ! })
let e1 = NSDate()
println(e1.timeIntervalSinceDate(s1))
// 0.0169369578361511
let s2 = NSDate()
var result2 = possibles.flatMap {
flatMap(, { [] }) ?? []
}
let e2 = NSDate()
println(e2.timeIntervalSinceDate(s2))
// 0.117663979530334
можно использовать reduce
:
let flattened = possibles.reduce([Int]()) {
if let x = { return + [x] } else { return }
}
вы все еще объявляете тип, но он немного менее навязчивый.
поскольку это то, что я, кажется, в конечном итоге делаю довольно много, я изучаю общую функцию для этого.
Я попытался добавить расширение в массив, чтобы я мог сделать что-то вроде 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({ != nil }).map({ ! })
}
possibles>!
// [1, 2, 3, 4, 5]
связанный с вопросом. Если вы применяете flatMap
для необязательного массива не забудьте дополнительно или принудительно развернуть массив, иначе он вызовет flatMap
on Optional
и не объекты, соответствующие Sequence
протокол. Однажды я совершил эту ошибку, например. если вы хотите удалить пустые строки:
var texts: [String]? = ["one", "two", "", "three"] // has unwanted empty string
let notFlatMapped = texts.flatMap({ .count > 0 ? : nil })
// ["one", "two", "", "three"], not what we want - calls flatMap on Optional
let flatMapped = texts?.flatMap({ .count > 0 ? : nil })
// ["one", "two", "three"], that's what we want, calls flatMap on Array