⚖️

Comparableの基礎

2025/01/25に公開

はじめに

ComparableはSwiftのプロトコルの一つで、カスタマイズした型の要素間での順序を定義するために使用される。これを採用することで、オブジェクトを比較したり、配列をソートしたりすることが可能になる。Comparableの主な目的は、1対1の順序比較を可能にし、それを基盤として、並べ替えや最小値・最大値の検索を実現することにある。

Appleの公式ドキュメント

特徴

1. 比較が可能になる

  • Comparableに準拠すると、カスタマイズしたの要素間で大小関係を定義できる。この定義に基づいて、 <, >, <=, >= のような比較演算子が使用可能になる。
  • Equatable プロトコルを継承しているため、== 演算子と != 演算子も使用できるようになる。

2. 特定の配列操作が実行可能となる

Comparableに準拠した型を要素とする配列で、sorted()min()max()などの操作が利用可能となる。

Comparableの基本構成

protocol Comparable: Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool
}

static func < を定義すれば、小なり( < )の定義に基づいてSwiftが他の操作( <, >, <=, >= )を自動的に推論し、利用可能になる。そして、完全な順序(total order)を決定できる。

完全な順序とは、任意の2つの値について、比較結果が必ず次の3つのいずれかに分類される状態を指す。

a < b // a は b より小さい
a == b // a と b は等しい
a > b // a は b より大きい

これにより、すべての値を一貫性のある方法で比較し、並べ替えたり、最小値や最大値を効率的に求めることが可能となる。

Comparableの実装

「推移律」

Appleのドキュメントでは、「推移律(transitivity)」を満たすことが重要であるとされている。
推移律とは、順序付けが一貫性を保つために必要な性質であり、次の条件を満たすものである。

a < b かつ b < c の場合、必ず a < c が成り立つ

推移律が守られないケース

struct CustomType: Comparable {
    let value: Int

    static func < (lhs: CustomType, rhs: CustomType) -> Bool {
        // 不正な順序付け例:特定の値では矛盾を発生させる
        if lhs.value == 2 && rhs.value == 4 {
            return false
        }
        return lhs.value < rhs.value
    }
}

let a = CustomType(value: 1)
let b = CustomType(value: 2)
let c = CustomType(value: 4)

// a < b が true
// b < c が true
// a < c が false となる

例1: 人物の年齢に基づいて比較する

struct Person: Comparable {
    var name: String
    var age: Int

    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.age < rhs.age
    }
}

例2: 時間と分で比較する

struct ClockTime: Comparable {
    var hour: Int
    var minute: Int

    static func < (lhs: Time, rhs: Time) -> Bool {
        if lhs.hour != rhs.hour {
            return lhs.hour < rhs.hour
        }
        return lhs.minute < rhs.minute
    }
}

利用可能になる配列の操作

以下のPerson構造体を例に、Comparableに準拠することで可能となる配列の操作を紹介する。

struct Person: Comparable {
    var name: String
    var age: Int

    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.age < rhs.age
    }
}

let people = [
    Person(name: "Alice", age: 30),
    Person(name: "Bob", age: 25),
    Person(name: "Charlie", age: 35)
]

1. sorted()

Comparableのデフォルト比較(<)に基づいて、配列を昇順に並べ替える。

let sortedPeople = people.sorted()
print(sortedPeople.map { "\($0.name) \($0.age)" }) 
// 出力: ["Bob 25", "Alice 30", "Charlie 35"]

降順で並べ替えたい場合はsorted(by:)を使う。
この場合、比較方法をクロージャで渡しているのでComparableへの準拠は不要である。

let sortedByAgeDescending = people.sorted(by: { $0.age > $1.age })
print(sortedByAgeDescending.map { "\($0.name) \($0.age)" })
// 出力: ["Charlie 35", "Alice 30", "Bob 25"]

2. min()max()

配列内の最小値や最大値を取得できる。

let youngest = people.min()!
let oldest = people.max()!

print("\(youngest.name) \(youngest.age)") // 出力: "Bob 25" (最も若い)
print("\(oldest.name) \(oldest.age)")     // 出力: "Charlie 35" (最も年上)

まとめ

Comparableに準拠することで、カスタマイズした型の大小比較が可能となり、sorted()min()max() などの機能を利用できる。

Discussion