web-dev-qa-db-ja.com

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!...配列タイプを指定する必要があることを除いて、私はこれらを恐れており、すべてのコストでそれらを避けます)。

コンテキストによってタイプを計算するが、強制的にアンラップしない代替手段はありますか?

22
MathewS

Swift 4.1以降、compactMapを使用できます。

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.compactMap { $0 }

(Swift 4.1では、flatMapの一部のオーバーロードがコンパクトマップに置き換えられました。これについて詳しく知りたい場合は、例をご覧ください。 https://useyourloaf.com/blog/replacing-flatmap-with-compactmap/

Swift 2b1を使用すると、簡単に行うことができます

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

1つの注意点(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/ を参照してください

38
Fizker

中間配列を1つだけ作成する最初の解決策はまだ好きです。次のように書くと、少しコンパクトになります

_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?
_

次に、簡単なパフォーマンス比較を示します(リリースモードでコンパイル)。これは、最初の方法が約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
_
15
Martin R

質問に関連しています。 flatMapをオプションの配列に適用する場合は、オプションで忘れずに配列を強制的にアンラップしてください。そうしないと、flatMapに対してOptionalが呼び出され、Sequenceプロトコル。私は一度間違いを犯しました。空の文字列を削除したい場合:

var texts: [String]? = ["one", "two", "", "three"] // has unwanted empty string

let notFlatMapped = texts.flatMap({ $0.count > 0 ? $0 : nil })
// ["one", "two", "", "three"], not what we want - calls flatMap on Optional

let flatMapped = texts?.flatMap({ $0.count > 0 ? $0 : nil })
// ["one", "two", "three"], that's what we want, calls flatMap on Array
0
Au Ris

reduceを使用できます:

let flattened = possibles.reduce([Int]()) { 
        if let x = $1 { return $0 + [x] } else { return $0 } 
    }

あなたはまだタイプを宣言しているようですが、それは少し控えめです。

0
letvargo

これは結局多くのことをしているように見えるものなので、これを行うための汎用関数を調査しています。

possibles.unwrapedのようなことができるようにArrayに拡張機能を追加しようとしましたが、Arrayに拡張機能を作成する方法を理解できませんでした。代わりに、カスタムオペレーターを使用しました。ここで最も困難な部分は、選択するオペレーターを特定することでした。最後に>!を選択して、配列が>でフィルタリングされ、次に!がラップ解除されていることを示しています。

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]
0
MathewS