星田さんの記事に対するコメント。
結論から言うとこうだ。
仕様上、Schemeの文字列比較関数には次のようなものがある。
- string-ci<=?
- string-ci<?
- string-ci=?
- string-ci>=?
- string-ci>?
- string<=?
- string<?
- string=?
- string>=?
- string>?
-ciが付いてるのと付いてないものの差は、大文字・小文字の違いを無視するか否か、だ。-ciは文字列"ABC"と"abc"を同じものとして扱う。要するに-ciはcase insensitiveの略だ。
前にも書いたけど、Schemeの場合、ビルトインデータ型に対して共通しそうな「処理」は敢えて分けてるけど一応キチンと準備はしてる。char<?の存在があり得るならstring<?の存在も「ある」って予想してよかったかも。ちと惜しかったね。
従って、例えば単純にソーティングするには次のように書けばいい。
次に一応、「文字列の比較」と言う事について概要を書いておこう。
そもそも「文字列」に大小があるのか、と言う話だ。
常識で考えるとそんなものは「無い」とも言える。文字列は大小比較の対象じゃないだろう、と。
しかし、結論から言うと、文字列の「並び」とは辞書での「並び」だ。辞書、とか言うとプログラミングやってると、ついつい「Pythonの辞書型?」とか思っちゃうが関係ない。ホンマモンの辞書の話だ。
まず、基本はアスキーコードなんだけど、平たく言うと、アスキーコード上では、文字Aから文字Zに向けて昇順に数値が振られていて、次に文字aからzに向けて昇順に数値が振られている。
これが基本となっている。
つまり、昇順に文字を並べていけば自然英語の辞書順にアルファベットが並んでいくわけだな。
これが「文字」の基本なんだけど、じゃあ文字列だとどうなんだろう。
文字列"A"と"B"を比較すると、文字列"A"に当てはめてる文字が指し示す数値は当然文字列"B"のものより小さい。
従って、
> (string<? "A" "B")
#t
>
は当然となる。
じゃあ二文字目以降はどうなんだろうか。
例えば一文字目が全く同じで、二文字目が違う場合は?
> (string<? "Aa" "Ab")
#t
>
となる。つまり、辞書的には"Aa"が付く項目は"Ab"が付く項目より先に来る、って事だ。これは辞書で考えても不思議じゃないだろう。
プログラミング的に言うと2つある文字列の1文字目が同じ場合、「文字の比較」は二文字目に移るようなプログラムになってる、って事だ。二文字目が同じな場合三文字目が・・・となる事が予想出来るだろう。そう、「文字列の比較」は再帰的処理だと言う事が予想出来るんだ。そして文字列の「どっかの文字」に差異が見られた時、それを理由に文字列の「大小」を言うようになってるわけだな。
これ、ちょっと考えてみれば分かるだろうけど、「文字列の比較」ってぇのは結構コストがかかる、って事だ。途中で「違う」ってのが分からない限り、文字列中の文字の比較は文字列の末尾に至るまで終わらない。
次の例も見てみよう。
> (string<? "Abc" "Ab")
#f
>
当然、文字列が途中まで同じ場合、より長い文字列の方が「大きく」なる。これも辞書を考えてみれば当然だろう。例えば、addはaddictより先に来る。
> (string<? "add" "addict")
#t
>
ここまでが辞書の「並び」の基本、だ。そして文字列比較の基本でもある。
でもじゃあ日本語はどうなるんだ?と。漢字は?
現代的な文字列エンコードの基本はほぼユニコードかないしはUTF-8になっている。2000年代なんかは、例えばWebブラウザで外国のページを見ると目出度く文字化けしたり、あるいは外国から日本のページを見ると正しくエンコードを指定しないとやっぱり文字化けしたが今はそういった事が殆どない。ユニコードやUTF-8のお陰である(※1)。
それで、だ。
日本語の扱い、つまり、平仮名、片仮名、漢字の扱いに於いてはJISで定義された漢字セット(※2)、具体的にはJIS X 0208、JIS X 0212、JIS X 0213とある程度互換性を持っている。
んで、日本語の場合、基本的には
- 平仮名->片仮名->漢字の順に数値が大きくなっている
- 漢字は音読みで50音順に並んでいる
- 漢字は画数が多い程後に来ている
と言うカンジだ。2番目と3番目を考え合わせると、ほぼ漢和辞典の漢字の並びに「適応させている」のが分かるだろう。
;; 両者とも音読みで「ア」だが、前者は7画、後者は8画の漢字で後者の方がより大きい数値が振られている> (string<? "亜" "阿")
#t
>
;; 「ほし」と「た」だと"田"の方が先に来そうだが、残念ながら"星"の音読みは「セイ」、"田"の音読みは「デン」なんで、文字コードで先に置かれてるのは"星"の方だ> (string<? "星" "田")
#t
>
;; 後の比較ルールはアスキーコードの場合と全く同じで、"星田オ"までは同じだが、"ク"と"ス"では"ク"の方が五十音順では先なので、"星田オクトパシー"の方が「小さい」と判定される> (string<? "星田オステオパシー" "星田オクトパシー")
#f
>
これが日本語の「文字列」の比較の仕組みだ。
例題の駅名なんかの場合でも、漢字で比較するのは可能だが、「音読み」と「画数」で比較される事となるんで、期待した順で並ばない、と言う事は重々考えられる。
例えば平仮名でソートすれば「青山一丁目」が最初に来るが、漢字でソートすると「青」の音読みは「セイ」なんで、もっと後に出てくる、と言った具合だ。
なお、今のPCのOSはほぼユニコードないしはUTF-8基準の文字コードを利用してるので、例えばファイルブラウザなんかでのファイル名あるいはフォルダ名のソーティング結果もこれらの文字コードの並び順の「規則」に従ってソーティングされる。
ちなみに、OCamlのコードのGaucheへの「直訳」は以下のようになるだろう。
※1: 2000年代辺りは例えばYahoo!JapanなんかはFreeBSD基準のEUC-JPなんかを用いていた為、このテの文字化けに悩まされる事が多かった。事実上、このテの文字コードの問題がWeb上で消え去ったのは2010年代に入ってから、と考えて良いんで、割に最近の話になるだろう。
※2: これらも歴史はそこそこ長くなってきてて、PC-9801時代から徐々に整理されて重版されてきたものだ。過去のPCで「ある漢字が使えても別の漢字が使えない」と言うのはこの仕様の拡張の取り込み具合によって変わっていた(当時は第一水準〜第四水準漢字、とか言ってたが)。
この当時、メモリ節約の為、漢字データはPC本体のROMに載せていたので、「全漢字を扱えるようにする」のも難しかったのである。