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:に変更