Cocoaの機能を使ったソートはいくつか方法がある。
1次元のリストをソートするときはNSArray
クラスのsortedArrayUsingSelector:
メソッドが簡単。
sortedArrayUsingSelector:に最適なselectorを考える
sortedArrayUsingSelector:
の引数はNSComparisonResult
クラスを返す比較用メソッドのselector
。
比較用メソッドとして最も汎用性が高いのはcompare:
。数字でも日付でも使える。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{5, 1, 2, 4, 3}
return (array's sortedArrayUsingSelector:"compare:") as list
--> {1, 2, 3, 4, 5}
use framework "Foundation"
set dateList to {date ("2015-01-01"), date ("2014-01-01"), date ("2016-01-01")}
set array to current application's NSArray's arrayWithArray:dateList
return (array's sortedArrayUsingSelector:"compare:") as list
--> {date "2014年1月1日水曜日 0:00:00", date "2015年1月1日木曜日 0:00:00", date "2016年1月1日金曜日 0:00:00"}
ただし、文字列に対しての結果はイマイチ。
文字列にcompare:
メソッドを使うとASCIIコードの番号で比較される。そのため、例えばアルファベットでは大文字(A-Z)、小文字(a-z)の順になる。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{"blue", "Apple", "cat", "Dog", "あし", "アオ", "あか", "アイ", "アザ"}
return (array's sortedArrayUsingSelector:"compare:") as list
--> {"Apple", "Dog", "blue", "cat", "あか", "あし", "アオ", "アザ", "アイ"}
文字列をソートするときには大文字と小文字を区別せず辞書順の方が自然。
文字列の比較に適したメソッドはIdentifying and Comparing Stringsのうちのひとつ、localizedCaseInsensitiveCompare:
。大文字と小文字を区別せず、さらにシステムの設定言語でローカライズされる。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{"blue", "Apple", "cat", "Dog", "あし", "アオ", "あか", "アイ", "アザ"}
return (array's sortedArrayUsingSelector:"localizedCaseInsensitiveCompare:") as list
--> {"Apple", "blue", "cat", "Dog", "アイ", "アオ", "あか", "アザ", "あし"}
ただ、こちらにも欠点がある。 localizedCaseInsensitiveCompare:
が比較できるのは文字列のみで、数字などには使えない。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{5, 1, 2, 4, 3}
return (array's sortedArrayUsingSelector:"localizedCaseInsensitiveCompare:") as list
--> error "-[__NSCFNumber localizedCaseInsensitiveCompare:]: unrecognized selector sent to instance 0x527" number -10000
つまりcompare:
とlocalizedCaseInsensitiveCompare:
は一長一短。
「リストの要素が文字列かどうか」によってselector
を切り替えるのが最適。リストの要素が文字列ならlocalizedCaseInsensitiveCompare:
、そうでなければcompare:
をselector
にする。
「リストの要素が文字列かどうか」を判別する方法
ここではリストの全要素が同じクラス、またはcompare:
メソッドがあるスーパークラスのサブクラス同士と仮定。
この仮定に基づき、1番目の要素だけをチェックする。したがって「リストの1番目の要素が文字列かどうか」を判別することになる。
NSArray
クラスの1番目の要素はfirstObject
プロパティで取得。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{"blue", "Apple", "cat", "Dog", "あし", "アオ", "あか", "アイ", "アザ"}
set anObject to array's firstObject()
return anObject's |description|() as text
--> "blue"
あるオブジェクトが文字列かどうかを判別するにはisSubclassOfClass:
メソッドを使う。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{"blue", "Apple", "cat", "Dog", "あし", "アオ", "あか", "アイ", "アザ"}
set anObject to array's firstObject()
return anObject's |class|'s isSubclassOfClass:(current application's NSString)
--> true
さらにリストが空である可能性を考慮して「リストの1番目の要素が文字列かどうか」を取得すると下のようになる。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{"blue", "Apple", "cat", "Dog", "あし", "アオ", "あか", "アイ", "アザ"}
set anObject to array's firstObject()
return anObject ≠ missing value and (anObject's |class|'s isSubclassOfClass:(current application's NSString))
--> true
リストの要素が文字列でない場合はもちろんfalse
。
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{5, 1, 2, 4, 3}
set anObject to array's firstObject()
return anObject ≠ missing value and (anObject's |class|'s isSubclassOfClass:(current application's NSString))
--> false
use framework "Foundation"
set array to current application's NSArray's arrayWithArray:{}
set anObject to array's firstObject()
return anObject ≠ missing value and (anObject's |class|'s isSubclassOfClass:(current application's NSString))
--> false
ハンドラ化
以上をまとめてハンドラ化。
use framework "Foundation"
my sortList({5, 1, 2, 4, 3})
--> {1, 2, 3, 4, 5}
my sortList({date ("2015-01-01"), date ("2014-01-01"), date ("2016-01-01")})
--> {date "2014年1月1日水曜日 0:00:00", date "2015年1月1日木曜日 0:00:00", date "2016年1月1日金曜日 0:00:00"}
my sortList({"blue", "Apple", "cat", "Dog", "あし", "アオ", "あか", "アイ", "アザ"})
--> {"Apple", "blue", "cat", "Dog", "アイ", "アオ", "あか", "アザ", "あし"}
my sortList({})
--> {}
on sortList(aList as list)
set array to current application's NSArray's arrayWithArray:aList
set anObject to array's firstObject()
if anObject ≠ missing value and (anObject's |class|'s isSubclassOfClass:(current application's NSString)) then
set comparator to "localizedCaseInsensitiveCompare:"
else
set comparator to "compare:"
end if
return (array's sortedArrayUsingSelector:comparator) as list
end sortList
更新履歴
- 2016-01-14: Cocoaの機能を使って作成
- 2016-01-15: リストの要素が文字列かどうかで
selector
を切り替えるように変更 - 2016-01-17: リストの要素が文字列のときの
selector
をlocalizedCaseInsensitiveCompare:
に変更