「自然順ソート」あれこれ
最近ちょっとしたツールを作っていて、スクリプト内で「Finderの表示順にファイルをソート(整列)させたい」という要件がありまして、なかなかに手こずったので今後のために備忘録としてまとめておきます。
Finderでのソート順って?
まず、Finderでのソート順って何かっていいますと、
ファイル1,ファイル2,ファイル3…ファイル10,ファイル11,ファイル12
となるようなソート方法のことです。文字列の後に数字が来るような場合でも数値順にソートさせたい。
perlなどで普通に文字列順にソートさせても数値でソートさせても
ファイル1,ファイル10,ファイル11,ファイル12,ファイル2,ファイル3…
となってしまい、思ったような処理ができません。
Finderでのソート順については、降りてきた天の声によると
@JunTajima FinderはJIS X 4061 照合順番を実装していると木田さんが言ってたような。
— 小形克宏 (@ogwata) 2016年9月11日
とのことらしく。ありがとうございます@ogwataさん。
なお、どうやらJIS X 4061 照合順番で定義されてるようなソート方法を「自然順ソート」と言うらしいのですが、PerlだとSort::Naturallyあたりのモジュールを利用すればできる模様(参考)。ただ今回は配布ツールでの使用を考えているので、CPANからモジュールを入れる的な話はできれば避けたく、いろいろと違う方向性を模索していたわけです。
Xojo内でCocoaのライブラリを呼び出してソート
フロントエンドのツールはXojoなので、Xojo内でできればなと思って、過去にQiitaにされていたアップされていたこちらの神エントリをもとにどうにかソートできるようにしたのが以下。さすがです@monokanoさん。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
'ファンクション宣言 Declare function localizedStandardCompare lib "Cocoa" selector "localizedStandardCompare:" (s1 as CFStringRef, s2 as CFStringRef) as Integer '最終出力用変数の定義 Dim mySortedArray() AS String '自然順ソート実行 For Each myItem As String In myArray If mySortedArray.Ubound = -1 then mySortedArray.Append (myItem) Else For myCount AS Integer = mySortedArray.Ubound Downto 0 Dim myMatchItem As String = mySortedArray(myCount) Dim myCompareResult AS Integer myCompareResult = localizedStandardCompare( myMatchItem, myItem ) If myCompareResult = - 1 then mySortedArray.Insert(myCount+1, myItem) Exit ElseIf myCount = 0 then mySortedArray.Insert(myCount, myItem) End If Next myCount End If Next Return mySortedArray() |
Xojo内でモジュール化しておいて元の配列を投げ、ソート後の配列を得る、みたいな使い方です。今回の目的としてはFinderのソート順の再現なので、Finderと同じエンジンを使うこちらが一番目的に叶いそうです。ただもちろんCocoaのライブラリを呼び出している以上Mac専用にはなります。
Applescriptでの自然順ソート
AppleScriptでもできそうかなということでこちらの投稿をもとに作ってみたのが以下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
on run (myOriginalJoinedString) --入力値を分割してリストに set mySplitArray to splitList(item 1 of myOriginalJoinedString, ",") --ソート実行 considering numeric strings set mySortedList to simple_sort(mySplitArray) end considering --リストを結合して文字列に set mySortedJoinString to joinList(mySortedList, ",") --結果を返す return mySortedJoinString end run --自然順ソートアルゴリズム on simple_sort(my_list) set the index_list to {} set the sorted_list to {} repeat (the number of items in my_list) times set the low_item to "" repeat with i from 1 to (number of items in my_list) if i is not in the index_list then set this_item to item i of my_list as text if the low_item is "" then set the low_item to this_item set the low_item_index to i else if this_item comes before the low_item then set the low_item to this_item set the low_item_index to i end if end if end repeat set the end of sorted_list to the low_item set the end of the index_list to the low_item_index end repeat return the sorted_list end simple_sort --リスト分割 on splitList(theText, aDelimiter) set tmp to AppleScript's text item delimiters set AppleScript's text item delimiters to aDelimiter set theList to get text items of theText set AppleScript's text item delimiters to tmp return theList end splitList --リスト結合 on joinList(theList, aDelimiter) set tmp to AppleScript's text item delimiters set AppleScript's text item delimiters to aDelimiter set theText to theList as string set AppleScript's text item delimiters to tmp return theText end joinList |
他の処理系に投げるので一回文字列に結合して引数として投げ、ソートした後に再結合した文字列を得ます。でそれをまた分解して配列に。
AppleScriptはテキストアイテムデリミタでの文字列の分解結合処理がいつもながらややこしいのですが、そのあたりはまあご愛敬ということで。こちらもAppleScriptを使う以上Mac専用です。まあそのうちAppleScriptで自然順ソートさせなきゃならないケースも出てくるかも知れないし。
PHPのnatsort関数でソート
PHPには自然順ソートができるnatsortという関数がデフォルトで入っているとのことなのでやってみたのが以下。
1 2 3 4 5 6 7 |
<?php $joinedOriginalString = $argv[1]; $myArray = explode(",", $joinedOriginalString); natsort($myArray); $joinedString = implode(",", $myArray); print($joinedString); ?> |
一番簡単かも。Mac環境に依存しないのでそういう意味での使い出もありそうです。
3種類の自然順ソートを収録したXojoのファイルをnatsort_sample.zip
◇
で、ブログにまとめてたら最後になんかPerlでモジュールを使わずにできそうなサンプルを発見してしまった。最初に出てきて欲しかった(汗)。
(2016.9.20)
追加情報メモ。
・ApplescriptならFinderにtellしてsort by nameで並べ替えちゃうのが手っ取り早いと。ただし処理は遅いから大量のファイル処理には向かないとのこと。
・OS Xでもバージョンによって微妙にソート結果が変わったりするらしい。なので確実にFinderのソート順通りのソート結果が欲しければApplescript上からFinderを呼び出して値を取得するか、Cocoaのライブラリを呼び出してソートさせるかするしかない。
・対象がファイル名ならPerl内からosascriptでApplescript呼んでFinderに聞くのが手っ取り早いんじゃないのと言われたのでちょい試しました。以下コードをメモ。
1 2 3 4 5 |
#! /usr/bin/perl my $imagePath = "/Users/JunTajima/Desktop/testfolder"; my $appleScriptPath = "/Users/JunTajima/Desktop/testapplescript.scpt"; my $result = `osascript $appleScriptPath $imagePath`; print $result; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
on run argv set myImageFolderPathPOSIX to item 1 of argv tell application "Finder" set myImageFolderPath to myImageFolderPathPOSIX as POSIX file set imageFilePaths to every item in folder myImageFolderPath set imageFilePaths to (sort imageFilePaths by name) set imageFileNamesList to {} repeat with filePath in imageFilePaths set the end of imageFileNamesList to filePath's name end repeat end tell set mySortedJoinString to joinList(imageFileNamesList, ",") return mySortedJoinString end run --リスト結合 on joinList(theList, aDelimiter) set tmp to AppleScript's text item delimiters set AppleScript's text item delimiters to aDelimiter set theText to theList as string set AppleScript's text item delimiters to tmp return theText end joinList |
行けますね。楽でいいかも。
タグ: Applescript, Finder, JIS X 4061, PHP, Xojo, ソート, 自然順ソート
2016/09/21 11:06
「Finderの表示順にファイルをソート(整列)させる」ためのライブラリを公開しています。
http://www.script-factory.net/XModules/FileSorter/index.html
ご参考になれば幸いです。
2016/09/21 13:05
おお、ありがとうございます!