[iOS] 配列をソート(並び替え)したい | Array, sort, sorted

2019/07/20

こんにちは。きんくまです。

配列のソート(並び替え)があやふやだったので、調べてみました。

sortとsorted

配列には sort と sorted があります。

sort
sortは配列の中身を変更します。

        var intArr = [10, 5, 3]
        intArr.sort()
        print("\(intArr)") // [3, 5, 10]
        
        var strArr = ["b", "c", "a"]
        strArr.sort()
        print("\(strArr)") // ["a", "b", "c"]

sorted
sortedは配列の中身を変更しないで、ソートされた新しい配列を返します

        let intArr = [10, 5, 3]
        let sortedIntArr = intArr.sorted()
        print("\(intArr)") // [10, 5, 3] 元のまま
        print("\(sortedIntArr)") // [3, 5, 10] ソートされてる
        
        let strArr = ["b", "c", "a"]
        let sortedStrArr = strArr.sorted()
        print("\(strArr)") // ["b", "c", "a"] 元のまま
        print("\(sortedStrArr)") // ["a", "b", "c"] ソートされてる

最近の関数型プログラムの流行からすると、元の配列を変更せず変更後のものを返す sorted を使うのが良さそう。

2パターンの引数

sort と sorted のどちらも、2パターンの引数をとります。

1. 引数なし(or by: > )

引数なしは、ソート対象の配列がComparableに対応している必要があります。

引数を(by: > )にすると逆順でソートされます。

        let intArr = [10, 5, 3]
        let sortedIntArr = intArr.sorted()
        print("\(sortedIntArr)") // [3, 5, 10]
        
        let sortedArr2 = arr.sorted(by: > )
        print("\(sortedArr2)") // [10, 5, 3]

2. 引数がBoolを返すクロージャー

Boolを返すクロージャーを引数にすることで、手軽にソートできます。

struct Person {
    var name: String
    var age: Int
}
let person1 = Person(name: "Foo", age: 25)
let person2 = Person(name: "Bar", age: 12)
let person3 = Person(name: "Baz", age: 48)
let people = [person1, person2, person3]

let sortedPeople = people.sorted(by: { lPerson, rPerson -> Bool in
    return lPerson.age < rPerson.age
})
print("\(sortedPeople)")

// 出力
// [Person(name: "Bar", age: 12), Person(name: "Foo", age: 25), Person(name: "Baz", age: 48)]

引数がBoolを返すクロージャー

もう少し詳しく見ていきます。

クロージャを引数にする場合は、Boolを返す必要があります。

Boolがtrueのときは、第一引数を先に配列に詰めようとする
反対にfalseのときは、第二引数を先に配列に詰めようとする

さきほどの例をもう少しわかりやすく書いてみます。

        let sortedPeople = people.sorted(by: { lPerson, rPerson -> Bool in
            if lPerson.age < rPerson.age {
                // true なのでlPersonの方がrPersonより先に配列に詰められる
                // この場合だと年齢が低い方を先に詰めることになる
                return true
            }
            return false
        })

クロージャーなので、ソートされるclassやstructを変更することなく、手軽に色々なソートパターンを定義できるのが良いポイントだと思います。

同じ年齢だったときのソート条件を付け足してみます。

let person1 = Person(name: "やまだ", age: 25)
let person2 = Person(name: "さとう", age: 12)
let person3 = Person(name: "たなか", age: 48)
let person4 = Person(name: "なかむら", age: 25)
let person5 = Person(name: "すずき", age: 25)
let people = [person1, person2, person3, person4, person5]

let sortedPeople = people.sorted(by: { person1, person2 -> Bool in
    // もし同じ年齢なら、名前順にする
    if person1.age == person2.age {
        // nameが比べられるのは、StringがComparableに対応しているから
        return person1.name < person2.name
    }
    return person1.age < person2.age
})

print("\(sortedPeople)")

//出力 同じ年齢のときは、名前順になっている
//[Person(name: "さとう", age: 12), 
// Person(name: "すずき", age: 25), 
// Person(name: "なかむら", age: 25), 
// Person(name: "やまだ", age: 25), 
// Person(name: "たなか", age: 48)]

Comparableに対応したい

Comparableに対応していると、引数なしでもソートできるみたいなのでやってみます。

さきほどのPerson型を適応してみましょう。適応するには、2つのstaticメソッドに対応していればOKです。

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

extension Person: Comparable {
    
    static func < (lPerson: Person, rPerson: Person) -> Bool {
        // 同じ年齢のときは名前順
        if lPerson.age == rPerson.age {
            return lPerson.name < rPerson.name
        }
        // 違う年齢のときは年齢の若い順
        return lPerson.age < rPerson.age
    }
    
    static func == (lPerson: Person, rPerson: Person) -> Bool {
        // 同じ年齢かつ同じ名前なら等しい順序とする
        if lPerson.age == rPerson.age &&
           lPerson.name == rPerson.name {
            return true
        }
        return false
    }
}

使ってみます

let person1 = Person(name: "やまだ", age: 25)
let person2 = Person(name: "さとう", age: 12)
let person3 = Person(name: "たなか", age: 48)
let person4 = Person(name: "なかむら", age: 25)
let person5 = Person(name: "すずき", age: 25)
let people = [person1, person2, person3, person4, person5]

// クロージャーなしで呼べた!
let sortedPeople = people.sorted()

print("\(sortedPeople)")

//出力 同じ年齢のときは、名前順になっている
//[Person(name: "さとう", age: 12), 
// Person(name: "すずき", age: 25), 
// Person(name: "なかむら", age: 25), 
// Person(name: "やまだ", age: 25), 
// Person(name: "たなか", age: 48)]

Comparableに適応させるかどうかは、ソートの頻度や、プロジェクトの内容によると思います。
様々なところで全部同じソート順で行う場合は適応させた方が良いと思います。
手軽に行うには、クロージャーで十分かと。

Comparableのページの下の方に、Conforming Typesという一覧が載っています。
この型であれば、適応ずみなのでソートが引数なしで呼べるということですね!

今回は配列のソートについてまとめてみました。ではでは。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

LINEスタンプ作りました!
毎日使える。とぼけたウサギ。LINEスタンプ販売中! 毎日使える。とぼけたウサギ

ページトップへ戻る