Help us understand the problem. What is going on with this article?

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:に変更
szk-3
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away