Hello, Swift ラバーなみなさん。これからラバーのみなさん。

突然ですが、Swift Algorithmsというものをご存知でしょうか?

2020 年に Apple から発表されたオープンソースパッケージです。
(パッケージとライブラリの差は Bundle(特定のディレクトリ構造)を持つかだそうです)

複雑になりがちな、シーケンス、コレクション処理をシンプルに記述できるようにするものです。

シンプルに記述できるようになるということは、実装時間が抑えられ、バグも減らせそうな気がしますね!

そちらを検証してきましょう。

導入

今回使うバージョン(2022 年 7 月時点最新バージョン)

  • Xcode: 13.4.1
  • Swift Algorithms: 1.0.0

導入手順は簡単です。

1.Swift Package Manager でURLを入力して導入するだけです。

手順は以下を参考にしてください。

  1. Xcodeツールバー>Add Packages…
  2. 右上のテキストフィールドにhttps://github.com/apple/swift-algorithmsを入力、「Add Package」をクリック
  3. DLできたら、「Add Package」をクリック

2.使用したいファイルで import します。

  • import Algorithms

現状できること

  • Combinations / permutations(組み合わせ/順列)
  • Mutating algorithms(突然変異アルゴリズム?配列を変化させます)
  • Combining collections(コレクションの組み合わせ)
  • Subsetting operations(サブセット化操作)
  • Partial sorting(部分的な並べ替え)
  • Other useful operations

この中にいろいろな関数があります。すこし掘り下げて見てみます。

Combinations / permutations

配列の中ですべての組み合わせがほしいときってありますよね(?)。

そんなときに便利なのが、combinations(ofCount:)関数です。

let numbers = [10, 20, 30, 40]
for combo in numbers.combinations(ofCount: 2) {
    print(combo)
}

// [10, 20]
// [10, 30]
// [10, 40]
// [20, 30]
// [20, 40]
// [30, 40]

といったようにofCount:に指定した桁数で組み合わせを作成してくれます。

以下のように、ofCount:を Range で指定することもできます。
そうすると、指定した桁数すべてで作成してくれます。

let numbers = [10, 20, 30]
for combo in numbers.combinations(ofCount: 2...3) {
    print(combo)
}

// [10, 20]
// [10, 30]
// [10, 40]
// [20, 30]
// [20, 40]
// [30, 40]
// [10, 20, 30]
// [10, 20, 40]
// [10, 30, 40]
// [20, 30, 40]

Combining collections

こちらもすべての組み合わせを作る関数ですが、配列ではなく、タプルで返却されるので、あとの操作が簡単です。
以下は、年と季節を組み合わせてリスト化するサンプルです。

let seasons = ["winter", "spring", "summer", "fall"]
for (year, season) in Algorithms.product(1900...2022, seasons) {
    print("\(year): \(season)")
}

// 1900: winter
// 1900: spring
...
// 2022: summer
// 2022: fall

ネストすることも可能です。

let years = 1900...2022
let months = ["January", "February", "March", "April"]
let days = 1...31

for (year, (month, day)) in Algorithms.product(years, Algorithms.product(months, days)) {
    print("\(year)-\(month)-\(day)")
}

// 1900-January-1
// 1900-January-2
...

返却値はタプルなので、変数にも入れられます。

Subsetting operations

compactMap()は配列中の nil を消し去ることができる便利アイテムですが、もっとシンプルに書くことができます。

let numbers = [10, nil, 20, nil, 30]

🤷‍♂️ let safeNumbers = numbers.compactMap { $0 }
🙆‍♂️ let safeNumbers = numbers.compacted()

// [10, 20, 30]

randomStableSample()は、配列中の要素をランダムに取り出します。
ランダムレコメンド的な使い方とかできそうですね!

var source = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

source.randomSample(count: 4)

// e.g. [30, 10, 70, 50]

uniqued()は、重複を削除します。

let numbers = [1, 2, 3, 3, 2, 3, 3, 2, 2, 2, 1]
🤷‍♂️ let set = Array(Set(numbers))
🙆‍♂️ let unique = numbers.uniqued()

// [1, 2, 3]

Other useful operations

chunked()を使い、手軽にグルーピングができます。

以下のようにすると、UITableView の Index のようなものが手軽に作れますね!

let lists = ["Alphonse", "Edward", "Winry", "Alex", "Envi", "Otosama"].sorted()

let prefixGroup = lists.chunked(on: \.first!)
for (id, name) in prefixGroup {
    print("\(id) Group: \(name)")
}

// A Group: ["Alex", "Alphonse"]
// E Group: ["Edward", "Envi"]
// O Group: ["Otosama"]
// W Group: ["Winry"]

また、以下のように Struct にも使えて、その場合、KeyPath を使用することで、要素の一つをグループにすることもできます。

struct Member {
    let name: String
    let grade: String
}

let results: [Member] = [
    .init(name: "エドワード", grade: "A"),
    .init(name: "アルフォンス", grade: "A"),
    .init(name: "ウィンリィ", grade: "B"),
    .init(name: "キング", grade: "C"),
    .init(name: "エンヴィー", grade: "C"),
    .init(name: "お父様", grade: "D")
]

// グループ化できる
let gradeGroup = results.chunked(on: \.grade)
for (grade, members) in gradeGroup {
    print("Grade:", grade)

    for member in members {
        print("\t\(member.name)")
    }
}

// Grade: A
//     エドワード
//     アルフォンス
// Grade: B
//     ウィンリィ
// Grade: C
//     キング
//     エンヴィー
// Grade: D
//     お父様

まとめ

あ!そういうのが欲しかった!みたいなものがありましたでしょうか??

このように、ちょっとした記述でかゆいところに手が届くような機能がいろいろあるオープンソースパッケージです。
オープンソースなので、ソースコードも見ることができ、実装の参考にもなりますね!

興味があれば、ぜひ一度使ってみてください!



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる