Posted at

Cocoaの機能を使って1次元リストをソートするAppleScriptハンドラ

More than 3 years have passed since last update.

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


ハンドラ化

以上をまとめてハンドラ化。


sortList.scpt

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: リストの要素が文字列のときのselectorlocalizedCaseInsensitiveCompare:に変更