dasukoの技術ブログ

現役エンジニアのブログです。

【Swift】シーケンスの高階関数まとめ

Swiftはまだまだ勉強中なので、Swiftでコレクション、シーケンスを扱う場合に重要な

高級関数をいくつか列挙し、まとめてみます!

シーケンスとは

順次要素へと反復アクセスするために用意されたプロトコルです。

シーケンスという言葉自体は、連続や順序といった意味を持ちます。

順番に並んだ一続きのデータを順番に処理することを指します。

 

シーケンスプロトコルについては

こちらに説明があります。

Apple Developer Documentation

今回紹介するシーケンスに定義されているクロージャを引数にとるメソッドは

クロージャ$0により各要素へアクセスすることができます。

 

 

それぞれの関数は結果の配列を返すので、

以下のようにいくつかの関数を連結して書くことができます。

let numbers = [0, 1, 2, nil, 3, 4]

let result = numbers
    .compactMap { $0 }
    .filter { $0 % 2 == 0}
    .map { "\($0)"}

print(result)

この例では 1. nilでない数値の配列に変換 2. 偶数の数値だけの配列に変換 3. 数値を文字列に変換

という処理をしています。

 

出力結果は以下のようになります。

["0", "2", "4"]

filter

filterろ過するという意味があります。

クロージャで指定した条件を満たす要素だけを含む配列を返します。

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

クロージャはその要素を結果に含めるかどうかを判定します。

結果に含める場合はtrueを返すようにします。

 

以下の例ではInt型の配列のうち、0より小さいものだけを含む配列に変換しています。

let numbers = [-1, 2, -4, 1]

let result = numbers.filter { $0 < 0 }
print(result)

 

↓結果

[-1, -4]

map

配列に対し引数で与えたクロージャマッピングし、その結果を含む配列を返します。

要するに配列の各要素を変換します。

func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]

次の例ではInt型の配列をString型の配列に変換しています。

let numbers = [1, 2, 3, 4, 5]

let result = numbers.map { "\($0)" }

print(result)

 

↓結果

["Optional(1)", "Optional(2)", "Optional(3)", "Optional(4)", "Optional(5)"]

ちなみに後述するcompactMapではnilを除外しますが、

以下のようにmapnilを除外しません。

let numbers = [1, 2, 3, nil, 5, 6]

let result = numbers.map { $0 }

print(result)

 

↓結果

[Optional(1), Optional(2), Optional(3), nil, Optional(5)]

compactMap

配列に対し引数で与えた変換(クロージャ)を加えた上でnilではない要素を返します。

引数のクロージャnilを返すと、その要素は配列から除外されます。

func compactMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

次の例では、nilを含んだInt型の配列をnilを除いた配列に変換しています。

let numbers = [1, 2, 3, nil, 5]

let result = numbers.compactMap { $0 }

print(result)

↓結果

[1, 2, 3, 5]

ちなみに返り値に対してnil判定をするので、変換後の値がnilでなければ除外されません。

 

以下の例ではnilを含んだIntの配列の要素それぞれをStringに変換していますが、

nilStringに変換した場合は"nil"という文字列であり、nilではないので除外されません。

let numbers = [1, 2, 3, nil, 5]

let result = numbers.compactMap { "\($0)" }

print(result)

 

↓結果

["Optional(1)", "Optional(2)", "Optional(3)", "nil", "Optional(5)"]

flatMap

flatは平らであることや、平坦であるという意味です。

flatMapmapと同様に各シーケンスの要素に対して変換処理を実行しますが、

その結果を一つの一次元配列に連結します。

func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

 

AppleのドキュメントにあるmapflatMapの比較の例がわかりやすいので、そのまま載せておきます。

mapでは一次元配列になるとは限りませんが、flatmapでは一次元配列となります。

let numbers = [1, 2, 3, 4]

let mapped = numbers.map { Array(repeating: $0, count: $0) }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]

let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

 

元々多次元配列の場合でも以下のように変換されます。

let numbers = [[1, 1], [2, 2], [3, 3]]

let result = numbers.flatMap { $0 }

print(result)

 

↓結果

[1, 1, 2, 2, 3, 3]

reduce

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Self.Element) throws -> Result) rethrows -> Result

指定したクロージャでシーケンスの各要素を組み合わせた結果を返します。

第一引数に初期値を指定し、第二引数に累積値と各要素を組み合わせ、新しい累積値に変換するクロージャを指定します。

以下の例では初期値を0とし、1〜4の値を順番に加算しています。

初期値は0なので最初のクロージャの引数のxとyは0と1になります。

0と1をの合計は1なので、次のxとyの値は1と2となります。

let numbers = [1, 2, 3, 4]
        
let result = numbers.reduce(0) { x, y in
    x + y
}
        
print(result)

 

全て合計すると、結果は10になります。

 

↓結果

10

もちろん乗算の場合に初期値を0にすると、結果は0になります。

let numbers = [1, 2, 3, 4]

let result = numbers.reduce(0) { x, y in
    x * y
}

print(result)

↓結果

0

lazy

シーケンスと同じ要素を返しますが、mapfilterなどの操作が遅延実行されます。

実際には、その値の取得時にシーケンスの高階関数が実行されます。

var lazy: LazySequence<Self> { get }

 

LazySequenceの定義はこんな感じです。

@frozen struct LazySequence<Base> where Base : Sequence

 

以下の例では先ほどのfilterの例で出した0以下の要素のみをもつ配列に変換していますが、

その変換を遅延実行しています。

そのため、"Test!!!!!!"というログよりも後に"map"というログが出力されています。

let numbers = [-1, 2, -4, 1]

let result = numbers
    .lazy
    .filter { $0 < 0 }
    .map({ v -> String in
        print("map")
        return "\(v)"
    })

print("Test!!!!!!")

print(Array(result))

 

↓結果

Test!!!!!!
map
map
["-1", "-4"]

次の出力結果は、上記の例からlazyを削除したものです。

その場合は当然"map"というログが最初に出力されます。

 

map
map
Test!!!!!!
["-1", "-4"]

参考

Apple Developer Documentation

シーケンス(シークエンス)とは - IT用語辞典 e-Words

イメージで理解するSwiftの高階関数(filter, map, reduce, compactMap, flatMap) - Qiita

Swift で map, compactMap, flatMap を使いこなそう - Qiita

Swiftの高階関数で遅延評価(lazy)を使い処理を効率化する - もちゅろぐ