0Pricing
Swift Academy · レッスン

コレクションでKeyPathを使う

KeyPathでコレクションを並べ替えたりmapしたりします。

「コレクションでKeyPathを使う」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

コレクションで活躍するキーパス

map、sorted、filterなどのコレクション操作では、各要素の1つのプロパティを読み取るだけの場合がよくあります。キーパスを使えば、クロージャーを書かずにその意図を表現できます。

キーパスはRootからValueへの関数として使えるため、単一の引数を受け取って値を返す関数が必要な場所ならどこでも、Swiftで使用できます。

struct Person {
    let name: String
    let age: Int
}

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let names = people.map(\.name)
print(names)

キーパス省略記法を使うmap

map(\.property)を渡すことは、そのプロパティを返すクロージャーを書くことと同じですが、より短く明快です。先頭のバックスラッシュとドットによって、コンパイラーは要素の型を推論できます。

以下の2つの形式を比較してください。どちらも同じ結果になります。

struct Person {
    let name: String
    let age: Int
}

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let closureForm = people.map { $0.age }
let keyPathForm = people.map(\.age)
print(closureForm)
print(keyPathForm)

キーパスによる比較を使うsorted(by:)

プロパティでソートするには、引き続き小さな比較クロージャーを記述しますが、その中でキーパスを使ってプロパティを読み取ると、意図が明確になります。

ここでは、ageの値を比較して、人々を若い順に並べます。

struct Person {
    let name: String
    let age: Int
}

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let path = \Person.age
let sorted = people.sorted { $0[keyPath: path] < $1[keyPath: path] }
print(sorted.map(\.name))

再利用できるソートヘルパー

任意の配列を、任意のComparableなプロパティでソートする汎用ヘルパーを作成できます。プロパティをキーパス引数として受け取ることで、コードベース全体でフィールドによるソートを1行で記述できます。

T: Comparableという制約により、そのプロパティを順序付けできることが保証されます。

func sorted<E, T: Comparable>(_ array: [E], by path: KeyPath<E, T>) -> [E] {
    array.sorted { $0[keyPath: path] < $1[keyPath: path] }
}

struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
print(sorted(people, by: \.age).map(\.name))

キーパスによるフィルタリング

filterにはBoolを返す述語が必要なため、プロパティだけのキーパスをそのまま使えるのは、プロパティがすでにBoolの場合に限られます。

ここでは各タスクがisDoneフラグを持っており、キーパスの省略記法を使ってそのフラグでフィルタリングします。

struct Task {
    let title: String
    let isDone: Bool
}

let tasks = [Task(title: "A", isDone: true), Task(title: "B", isDone: false)]
let finished = tasks.filter(\.isDone)
print(finished.map(\.title))

Bool以外のプロパティでフィルタリングする

Bool以外のプロパティでフィルタリングする場合は、クロージャー内でキーパスを使ってそのプロパティを読み取り、比較します。汎用ヘルパーを使えば、このパターンを任意のEquatableなプロパティに適用できます。

ここでは、対象の都市と一致する人だけを残します。

struct Person { let name: String; let city: String }

func filter<E, T: Equatable>(_ array: [E], _ path: KeyPath<E, T>, equals value: T) -> [E] {
    array.filter { $0[keyPath: path] == value }
}

let people = [Person(name: "Ana", city: "Oslo"), Person(name: "Ben", city: "Rome")]
print(filter(people, \.city, equals: "Oslo").map(\.name))

キーパスでmaxとminを使う

max(by:)とmin(by:)メソッドは、sortedと同じように比較クロージャーを受け取ります。キーパスを使ってプロパティを読み取り、最大または最小の値を持つ要素を見つけます。

ここでは最年長の人を見つけます。

struct Person { let name: String; let age: Int }

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25), Person(name: "Cy", age: 41)]
let path = \Person.age
if let oldest = people.max(by: { $0[keyPath: path] < $1[keyPath: path] }) {
    print(oldest.name)
}

reduceでプロパティを合計する

map(\.property)とreduceを組み合わせると、数値フィールドの合計を求められます。まず各要素から目的の数値を取り出し、それらを加算します。

ここでは、1つのパイプラインですべての年齢を合計します。

struct Person { let name: String; let age: Int }

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let totalAge = people.map(\.age).reduce(0, +)
print(totalAge)

キーパスでグループ化する

Dictionary(grouping:by:)は、クロージャーの結果ごとに要素を振り分けます。キーパスを使ってプロパティを読み取ることで、そのフィールドを基準にグループ化できます。

ここでは、人々を都市ごとにグループ化し、配列を値とする辞書にまとめます。

struct Person { let name: String; let city: String }

let people = [
    Person(name: "Ana", city: "Oslo"),
    Person(name: "Ben", city: "Rome"),
    Person(name: "Cy", city: "Oslo")
]
let path = \Person.city
let byCity = Dictionary(grouping: people, by: { $0[keyPath: path] })
print(byCity["Oslo"]?.map(\.name) ?? [])

複数フィールドのソート

1つのフィールドでソートし、同順位の場合に別のフィールドで順序を決めるには、まず主キーのキーパスを比較し、主キーが等しい場合は副キーに切り替えます。

ここでは年齢でソートし、年齢が同じ場合は名前でソートします。

struct Person { let name: String; let age: Int }

let people = [
    Person(name: "Zoe", age: 30),
    Person(name: "Ana", age: 30),
    Person(name: "Ben", age: 25)
]
let sorted = people.sorted {
    if $0[keyPath: \Person.age] != $1[keyPath: \Person.age] {
        return $0.age < $1.age
    }
    return $0.name < $1.name
}
print(sorted.map(\.name))

ここでキーパスがクロージャーより優れている理由

コレクション呼び出しでキーパスを使うと、定型コードが減り、クロージャー本体のタイプミスも防げます。また、単一のフィールドを取り出しているという意図が明確になります。

複雑な変換には今でもクロージャーが適していますが、単純なプロパティの読み取りには\.propertyが最も明快です。

struct Product { let name: String; let price: Double }

let products = [Product(name: "Pen", price: 2), Product(name: "Pad", price: 5)]
let names = products.map(\.name)
let prices = products.map(\.price)
print(names)
print(prices)

確認:mapのキーパス

各要素から1つのプロパティを取り出す、簡潔なキーパス形式を選んでください。

まとめ:コレクションのキーパス

キーパスによってコレクションのコードを簡潔にできることを学びました。

  • map(\.property)は、クロージャーを使わずに1つのフィールドを取り出します。
  • filter(\.boolProperty)はBoolフィールドに直接使えます。それ以外の場合は[keyPath:]を使うクロージャーを使用します。
  • ソート、max、minでは、比較クロージャー内でキーパスを使ってプロパティを読み取ります。
  • KeyPathを受け取る汎用ヘルパーにより、フィールドによるソートやフィルタリングを再利用可能な1行のコードにできます。

次は、値を読み取るだけでなく設定もできる書き込み可能キーパスについて学びます。

struct Person { let name: String; let age: Int }

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let youngestFirst = people.sorted { $0[keyPath: \Person.age] < $1[keyPath: \Person.age] }
print(youngestFirst.map(\.name))

よくある質問

「コレクションでKeyPathを使う」レッスンは無料ですか?

はい。「コレクションでKeyPathを使う」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「コレクションでKeyPathを使う」で何を学びますか?

KeyPathでコレクションを並べ替えたりmapしたりします。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「コレクションでKeyPathを使う」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. KeyPathの基礎
  2. コレクションでKeyPathを使う
  3. 書き込み可能なKeyPathと参照KeyPath
  4. @dynamicMemberLookup
← Swift Academyに戻る