こんにちは。きんくまです。
配列のソート(並び替え)があやふやだったので、調べてみました。
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という一覧が載っています。
この型であれば、適応ずみなのでソートが引数なしで呼べるということですね!
今回は配列のソートについてまとめてみました。ではでは。
■ 自作iPhoneアプリ 好評発売中!
・フォルメモ - シンプルなフォルダつきメモ帳
・ジッピー電卓 - 消費税や割引もサクサク計算!
■ LINEスタンプ作りました!
毎日使える。とぼけたウサギ