パソコンを使っているとファイル名一覧を見る機会は頻繁にある。 ファイル名一覧を出力する多くのソフトは見易いように項目の内容でソートする機能を持っている。 ファイル名の順序で並び換える場合は一般的には「辞書順」である。 辞書順というのはふたつの文字列の先頭から比較していって異なるところの文字を比較することで文字列の大小が決定される方法である。

ウィンドウズのエクスプローラもソート機能を持っているが、ファイル名の順序でソートしたときにファイル名に含まれる数値の部分を特別扱いする。 たとえば abc5.txt というファイルと abc40.txt というふたつのファイルが有ったとき、伝統的な辞書順では abc40.txt の方が小さいが、エクスプローラは abc5.txt の方を小さいと判断する。 一般的な人間の感覚としてはその方が感覚に一致するのだろう。 私は古い世代の人間であるから、いまだにゼロパディングして桁数を合わせてしまうのだが。

なんとなく思い立ってこのルールを Scheme で実装してみた。 R7RS の形式にしてある。

( define-library ( filename-compare ) ( export filename<? filename>? filename=? filename<=? filename>=? ) ( import ( scheme base ) ( scheme char )) ( begin ( define ( read-integer port ) ( do (( ch ( peek-char port ) ( peek-char port )) ( acc 0 ( + ( * acc 10 ) ( digit-value ch )))) (( or ( eof-object? ch ) ( not ( char-numeric? ch ))) acc ) ( read-char port ))) ( define ( filename-compare str1 str2 ) ( let (( port1 ( open-input-string str1 )) ( port2 ( open-input-string str2 ))) ( let loop (( ch1 ( peek-char port1 )) ( ch2 ( peek-char port2 ))) ( cond (( eof-object? ch1 ) ( if ( eof-object? ch2 ) ' eq ' lt )) (( eof-object? ch2 ) ' gt ) (( and ( char-numeric? ch1 ) ( char-numeric? ch2 )) ( let (( num1 ( read-integer port1 )) ( num2 ( read-integer port2 ))) ( if ( = num1 num2 ) ( loop ( peek-char port1 ) ( peek-char port2 )) ( if ( < num1 num2 ) ' lt ' gt )))) (( char-ci=? ch1 ch2 ) ( read-char port1 ) ( read-char port2 ) ( loop ( peek-char port1 ) ( peek-char port2 ))) ( else ( if ( char-ci<? ch1 ch2 ) ' lt ' gt )))))) ( define ( filename<? x y ) ( eqv? ( filename-compare x y ) ' lt )) ( define ( filename>? x y ) ( eqv? ( filename-compare x y ) ' gt )) ( define ( filename=? x y ) ( eqv? ( filename-compare x y ) ' eq )) ( define ( filename<=? x y ) ( let (( r ( filename-compare x y ))) ( or ( eqv? r ' lt ) ( eqv? r ' eq )))) ( define ( filename>=? x y ) ( let (( r ( filename-compare x y ))) ( or ( eqv? r ' gt ) ( eqv? r ' eq )))) ))

これは私の理解を形にしたものであって、ウィンドウズの実装と一致することを検証しているわけではない。

Document ID: 7d30c7ea6aad074df0def7719562b113