TL;DR
スイフト4
を使用しarray.compactMap { $0 }
ます。Apple はフレームワークを更新して、バグや混乱を引き起こさないようにしました。
スイフト3
array.flatMap { $0 }
潜在的なバグや混乱を避けるために、nil を削除するために使用しないでください。array.removeNils()
代わりに (以下の実装、Swift 3.0 用に更新)などの拡張メソッドを使用します。
ほとんどの場合は機能しますが、拡張機能array.flatMap { $0 }
を使用する理由がいくつかあります。array.removeNils()
removeNils
やりたいことを正確に説明します:値を削除しnil
ます。よく知らない人flatMap
が調べる必要があり、調べたときによく注意すれば、次のポイントと同じ結論に達するでしょう。
flatMap
には、2つのまったく異なることを行う 2 つの異なる実装があります。型チェックに基づいて、コンパイラはどちらが呼び出されるかを決定します。型推論が頻繁に使用されるため、これは Swift では非常に問題になる可能性があります。(たとえば、変数の実際の型を判断するには、複数のファイルを検査する必要がある場合があります。) リファクタリングにより、アプリが間違ったバージョンを呼び出し、見つけにくいバグにflatMap
つながる可能性があります。
- 2 つのまったく異なる機能があるため、この 2 つを簡単に混同
flatMap
できるため、理解がはるかに難しくなります。
flatMap
はオプションではない配列 (例: ) で呼び出すことができるため、 からまで[Int]
の配列をリファクタリングすると、コンパイラが警告しない呼び出しが誤って取り残される可能性があります。せいぜい自分自身を返すだけですが、最悪の場合、他の実装が実行され、バグにつながる可能性があります。[Int?]
[Int]
flatMap { $0 }
- Swift 3 では、戻り値の型を明示的にキャストしないと、コンパイラは間違ったバージョンを選択し、意図しない結果を引き起こします。(以下の Swift 3 セクションを参照)
- 最後に、型チェック システムがオーバーロードされた関数のどれを呼び出すかを決定する必要があるため、コンパイラの速度が低下します。
要約すると、問題の関数には 2 つのバージョンがあり、残念ながらflatMap
.
ネスト レベルを削除してシーケンスをフラット化します (例: [[1, 2], [3]] -> [1, 2, 3]
)
public struct Array<Element> : RandomAccessCollection, MutableCollection {
/// Returns an array containing the concatenated results of calling the
/// given transformation with each element of this sequence.
///
/// Use this method to receive a single-level collection when your
/// transformation produces a sequence or collection for each element.
///
/// In this example, note the difference in the result of using `map` and
/// `flatMap` with a transformation that returns an array.
///
/// let numbers = [1, 2, 3, 4]
///
/// let mapped = numbers.map { Array(count: $0, repeatedValue: $0) }
/// // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
///
/// let flatMapped = numbers.flatMap { Array(count: $0, repeatedValue: $0) }
/// // [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
///
/// In fact, `s.flatMap(transform)` is equivalent to
/// `Array(s.map(transform).joined())`.
///
/// - Parameter transform: A closure that accepts an element of this
/// sequence as its argument and returns a sequence or collection.
/// - Returns: The resulting flattened array.
///
/// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
/// and *n* is the length of the result.
/// - SeeAlso: `joined()`, `map(_:)`
public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element]
}
シーケンスから要素を削除します (例: [1, nil, 3] -> [1, 3]
)
public struct Array<Element> : RandomAccessCollection, MutableCollection {
/// Returns an array containing the non-`nil` results of calling the given
/// transformation with each element of this sequence.
///
/// Use this method to receive an array of nonoptional values when your
/// transformation produces an optional value.
///
/// In this example, note the difference in the result of using `map` and
/// `flatMap` with a transformation that returns an optional `Int` value.
///
/// let possibleNumbers = ["1", "2", "three", "///4///", "5"]
///
/// let mapped: [Int?] = numbers.map { str in Int(str) }
/// // [1, 2, nil, nil, 5]
///
/// let flatMapped: [Int] = numbers.flatMap { str in Int(str) }
/// // [1, 2, 5]
///
/// - Parameter transform: A closure that accepts an element of this
/// sequence as its argument and returns an optional value.
/// - Returns: An array of the non-`nil` results of calling `transform`
/// with each element of the sequence.
///
/// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
/// and *n* is the length of the result.
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
}
{ $0 }
#2は、として渡すことでnilを削除するために人々が使用するものtransform
です。メソッドがマップを実行し、すべてのnil
要素を除外するため、これは機能します。
「Apple はなぜ #2 を"に改名しなかったのか?」removeNils()
と疑問に思うかもしれません。心に留めておくべきことの 1 つは、flatMap
nil を削除するために使用することが #2 の唯一の使用法ではないということです。実際、どちらのバージョンもtransform
関数を使用するため、上記の例よりもはるかに強力になる可能性があります。
たとえば、#1 は簡単に文字列の配列を個々の文字に分割 (フラット化) し、各文字を大文字にする (マップ) ことができます。
["abc", "d"].flatMap { $0.uppercaseString.characters } == ["A", "B", "C", "D"]
番号#2はすべての偶数を簡単に削除(平坦化)し、各数値に-1
(マップ)を掛けることができます:
[1, 2, 3, 4, 5, 6].flatMap { ($0 % 2 == 0) ? nil : -$0 } == [-1, -3, -5]
(この最後の例では、明示的な型が記述されていないため、Xcode 7.3 が非常に長い時間スピンする可能性があることに注意してください。メソッドに異なる名前を付ける必要がある理由についてのさらなる証拠です。)
やみくもにsを使っflatMap { $0 }
て削除する本当の危険は、 を呼び出すときではなく、 のようなものを呼び出すときに起こります。前者の場合、無害に呼び出し #2 を呼び出し、 を返します。後者の場合、同じことをすると思うかもしれませんが (値がないため無害に戻ります)、呼び出し #1 を使用しているため、実際には戻ります。nil
[1, 2]
[[1], [2]]
[1, 2]
[[1], [2]]
nil
[1, 2]
flatMap { $0 }
sを削除するために使用されるという事実は、Apple からのものではなくnil
、Swiftコミュニティの 推奨事項のようです。おそらく、Apple がこの傾向に気付いた場合、最終的にremoveNils()
機能または同様のものを提供するでしょう。
それまでは、独自のソリューションを考え出す必要があります。
解決
// Updated for Swift 3.0
protocol OptionalType {
associatedtype Wrapped
func map<U>(_ f: (Wrapped) throws -> U) rethrows -> U?
}
extension Optional: OptionalType {}
extension Sequence where Iterator.Element: OptionalType {
func removeNils() -> [Iterator.Element.Wrapped] {
var result: [Iterator.Element.Wrapped] = []
for element in self {
if let element = element.map({ $0 }) {
result.append(element)
}
}
return result
}
}
(注: ... と混同しないでください。この投稿で説明しelement.map
たこととは何の関係もありません。ラップ解除できるオプションの型を取得するためにの関数を使用しています。この部分を省略すると、この部分が取得されます。構文エラー:「エラー: 条件付きバインディングの初期化子には、'Self.Generator.Element' ではなく、オプションの型が必要です。」どのように役立つかについての詳細は、非 nil をカウントするために SequenceType に拡張メソッドを追加することについて書いたこの回答を参照してください。 .)flatMap
Optional
map
map()
使用法
let a: [Int?] = [1, nil, 3]
a.removeNils() == [1, 3]
例
var myArray: [Int?] = [1, nil, 2]
assert(myArray.flatMap { $0 } == [1, 2], "Flat map works great when it's acting on an array of optionals.")
assert(myArray.removeNils() == [1, 2])
var myOtherArray: [Int] = [1, 2]
assert(myOtherArray.flatMap { $0 } == [1, 2], "However, it can still be invoked on non-optional arrays.")
assert(myOtherArray.removeNils() == [1, 2]) // syntax error: type 'Int' does not conform to protocol 'OptionalType'
var myBenignArray: [[Int]?] = [[1], [2, 3], [4]]
assert(myBenignArray.flatMap { $0 } == [[1], [2, 3], [4]], "Which can be dangerous when used on nested SequenceTypes such as arrays.")
assert(myBenignArray.removeNils() == [[1], [2, 3], [4]])
var myDangerousArray: [[Int]] = [[1], [2, 3], [4]]
assert(myDangerousArray.flatMap { $0 } == [1, 2, 3, 4], "If you forget a single '?' from the type, you'll get a completely different function invocation.")
assert(myDangerousArray.removeNils() == [[1], [2, 3], [4]]) // syntax error: type '[Int]' does not conform to protocol 'OptionalType'
[1, 2, 3, 4]
( removeNils() が を返すことが期待されていたのに、最後のもので flatMap がどのように返されるかに注意してください[[1], [2, 3], [4]]
。)
解決策は、リンクされている@fabbの回答に似ています。
ただし、いくつかの変更を加えました。
- method には名前を付けませんでした。シーケンス型
flatten
のメソッドが既に存在flatten
し、まったく異なるメソッドに同じ名前を付けたことが、そもそもこの混乱に陥った原因です。言うまでもなく、それよりも何をするかを誤解する方がはるかに簡単flatten
ですremoveNils
。
T
で新しいタイプを作成するのではなく、( ) を使用OptionalType
するのと同じ名前を使用します。Optional
Wrapped
- 時間につながる を実行する
map{}.filter{}.map{}
代わりにO(M + N)
、配列を 1 回ループします。
flatMap
から に移動するGenerator.Element
代わりに を使用しGenerator.Element.Wrapped?
ますmap
。関数nil
内で値を返す必要はないので、それで十分です。関数を回避することで、まったく異なる関数を持つ同じ名前のさらに別の (つまり 3 番目の) メソッドを混同することが難しくなります。map
map
flatMap
removeNils
vs.を使用することの 1 つの欠点flatMap
は、型チェッカーがもう少しヒントを必要とする場合があることです。
[1, nil, 3].flatMap { $0 } // works
[1, nil, 3].removeNils() // syntax error: type of expression is ambiguous without more context
// but it's not all bad, since flatMap can have similar problems when a variable is used:
let a = [1, nil, 3] // syntax error: type of expression is ambiguous without more context
a.flatMap { $0 }
a.removeNils()
あまり調べていませんが、追加できるようです:
extension SequenceType {
func removeNils() -> Self {
return self
}
}
オプションではない要素を含む配列でメソッドを呼び出せるようにしたい場合。これにより、大規模な名前変更 (例: flatMap { $0 }
-> removeNils()
) が容易になります。
自分自身への割り当ては、新しい変数への割り当てとは異なります?!
次のコードを見てください。
var a: [String?] = [nil, nil]
var b = a.flatMap{$0}
b // == []
a = a.flatMap{$0}
a // == [nil, nil]
驚くべきことに、にa = a.flatMap { $0 }
割り当てた場合は nil を削除しませんが、 !に割り当てた場合は nil を削除します。私の推測では、これはオーバーロードされたものと関係があり、Swift が使用するつもりのないものを選択したことと関係があると思います。a
b
flatMap
予想されるタイプにキャストすることで、問題を一時的に解決できます。
a = a.flatMap { $0 } as [String]
a // == []
しかし、これは忘れがちです。代わりに、removeNils()
上記の方法を使用することをお勧めします。
アップデート
の(3)オーバーロードの少なくとも1つを非推奨にする提案があるようですflatMap
:https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md