Lisp/基本からさらに一歩進んで/文字列

Common Lisp での文字列の扱いについて知らなければならない最も重要なことは、文字列は配列であり、したがって連続している順序でもあるということです。これは配列と順序に適用される全ての概念が文字列に利用することも出来る、ということを意味します。もし、特定の文字列操作関数が見つからなければ、必ずもっと一般的な配列か順序に関する関数を探した方がいいでしょう。ここでは文字列に対して出来ることをほんの少しだけ取り扱います。
文字列は連続しているので、部分文字列を取り出すには SUBSEQ 関数を使用します。文字列の添え字(index)は常に 0 から始まります。三番目の引数は任意で、ここから部分文字列を構成しない最初の文字の添え字です。部分文字列の終端の1個後の添え字と憶えてもいいかもしれません。部分文字列の長さを指定するものではないので注意しましょう。
SETF と SUBSEQ を一緒に使えば部分文字列を操作することも出来ます。
しかし、文字列は "伸縮可能" ではないということには注意してください。 HyperSpec から引用すると、 "部分列と新しい列が等しい長さでなければ、短い方が入れ替えられた列の要素数を決定する" ということになります。例を挙げると以下のようになります。
文字列の個別の文字を取り出すために、関数 CHAR を使用することができます。 CHAR は SETF と連結して使うことができます。
SCHAR もあるということにも注意です。効率が重要なら、 SCHAR を適切に配置すれば少しだけ高速になります。
文字列は配列であり、したがって連続した順序でもあるので、もっと包括的な関数である AREF や ELT を使用することもできます(これは CHAR がより効率的に利用されるのに対して、より一般的ということになります。)
文字列操作に使われる関数はたくさんありますので、ここではいくつかのサンプルを示すにとどめます。これ以上は HyperSpec のシーケンス辞典を参照してください。
他の頻繁に使用される関数は replace-all です(ただしANSI規格標準ではありません)この関数は、文字列の 検索/置換 操作の簡単な機能を提供しています。文字列の全ての '部分' の存在が '代替部分' と置き換えられた新しい文字列が返り値として返ります。
replace-all の実装は以下のようなものになります。
しかし、上記のコードは長い文字列については能率的に利用されていないということを憶えておいてください。もし非常に長い文字列やファイルなどの操作を実行しようとするのなら、ぜひ、非常に最適化された cl-ppcre の正規表現と文字列操作ライブラリの使用を検討してください。
名前が全てを示すとおり、 CONCATENATE はあなたの友達となります。これは一般的な手続き関数なので、返り値の型を最初の引数として与えなければなりません。
もしたくさんの部分から文字列を生成しなければならないとしたら、いつも CONCATENATE を呼び出すのは非効率になるでしょう。あなたのデータが何であるかによって、少なくとも他に3つの文字列の断片を生成する良い方法があります。もし一回につき一文字ずつ文字列を作るのなら、調節可能な VECTOR(1次元の配列)型で 0 の fill-pointer の文字を作成し、それから VECTOR-PUSH-EXTEND を使用すると良いでしょう。これで文字列がどれくらいの長さになるかの概算が可能かどうか、システムに対してヒントを与えることも出来るようになります(VECTOR-PUSH-EXTEND の任意の三番目の引数を見てください)
もし文字列がシンボルや数値、文字、文字列など任意のオブジェクト(かつプリントされた表現)で構成されるなら、出力ストリームの引数を NIL にした FORMAT を使用することができます。これは文字列としての出力で表示された返り値を直接 FORMAT します。
FORMAT のミニ言語としての Loop 構造を用いて CONCATENATE の動作を模倣することが出来ます。
FORMAT ではもっとたくさんの処理が可能なのですが、しかし相対的に難解な構文をしています。最後の例の後で、 CLHS の項でフォーマット出力についての詳細を確認できるでしょう。
様々なオブジェクトによってプリント表示される文字列を作成するまた別の方法は WITH-OUTPUT-TO-STRING です。このお手軽なマクロの値は、マクロの記述部分を伴った文字列ストリームに出力された全てを含む文字列です。これはつまりあなたの采配において FORMAT の全能力を使えるということを意味します。これは必ず必要なるでしょう。
前の項ではこれをするための十分なヒントが与えられているのですが、どのように区切り文字を使って文字列を連結するかを強調するのはここがちょうどいいタイミングかもしれません。 (192 168 1 1) あるいは ("192" "168" "1" "1") のような数値か文字列のリストを持っていると考えてください。そして別の文字列を作成するために、それらを区切り文字 "." あるいは ";" で連結したいとします。以下にいくつかの例を表してみました。
文字列を一回に一文字ずつ処理するには MAP 関数を使用します。
あるいは LOOP でも同じことが出来ます。
文字で文字列を反転させるのは組み込みの REVERSE 関数を使用すれば簡単です。あるいは同じ機能ですが破壊的作用を持つ NREVERSE でも可能です。
Common Lisp では Perl の split と join で実現できるような、単語での文字列の反転を一行で書ける機能はありません。実現するには外部ライブラリの SPLIT-SEQUENCE を使うか、あるいは自分で解決法を実装するしかありません。以下の例で実装を試みています。
Common Lisp では文字列の大文字、小文字を操作する関数がいくつもあります。
これらの関数は :START や :END のようなキーワード引数をとることが出来ます。これを使用して任意で文字列の一部分だけを操作することが出来ます。これらの関数は全て、関数名の最初に "N" をつけると、同じ機能の破壊的な作用を持つ関数となります。
HyperSpec によると "STRING-UPCASE や STRING-DOWNCASE, STRING-CAPITALIZE では文字列は変更されません。しかし、文字列のどの文字も変更の必要が無いということならば、実装の裁量によっては、結果はその文字列そのものか、文字列のコピーということになるでしょう。" ということになるそうです。これは次の例の最後の返り値は実装によっては "BIG" にも "BUG" にもなるということです。もし結果を確実にしたいなら、 COPY-SEQ を利用してください。
空白文字を取り除くことが出来るだけではなく、任意の文字列を取り除くことが出来ます。 STRING-TRIM, STRING-LEFT-TRIM そして STRING-RIGHT-TRIM は第2引数の最初、あるいは最後、もしくは両方から、第1引数で指定した文字を全て取り除いた部分文字列を返します。第1引数はどんな連続した文字列でもかまいません。
大文字、小文字の操作の項で言及されていた警告はここでも適用されます。
関数 INTERN は文字列をシンボルに変換します。実際にはこの関数は第一引数の文字列として表されるシンボルが既にパッケージの中でアクセス可能かどうかをチェックし、必要であれば入力します。任意の第二引数はデフォルトで現在使用しているパッケージが設定されています。この関数の全てのコンセプトを説明し、二番目の返り値について言及するのはこの章の範囲を超えますので、 パッケージの詳細については、CLHS の章を参照してください。
この文字列のケースの関連性に注目してください。
反対に、シンボルから文字列に変換するには SYMBOL-NAME か STRING を使用します。
関数 COERCE を使用することで長さ1の文字列を文字に変換することが出来ます。 COERCE ではどんな連続した文字でも文字列に変換することも出来ます。しかし、 COERCE を使って一つの文字を文字列に変換することは出来ません。それには STRING を使用しなければなりません。
関数 FIND, POSITION そして、ほぼ同じ機能を持った FIND-IF, POSITION-IF を使用することで文字列中の文字を検索することが出来ます。
あるいは COUNT や COUNT-IF を使用することで文字列中の文字を数えることが出来ます。
関数 SEARCH は文字列中の部分文字列を検索することが出来ます。
Common Lisp は文字列で表された数字を、対応する数値の値に変換する PARSE-INTEGER という関数があります。二番目の返り値は文字列の解析が止まった場所の添え字となります。
PARSE-INTEGER は #X のような基数の指定子を解釈しませんし、他の数値型を解析する組み込み関数もありません。このようなケースでは READ-FROM-STRING を使用することができますが、この関数を使用する場合は十全な読み込み解釈が有効になっていることに注意してください。
Common Lisp では PRINC-TO-STRING や PRIN1-TO-STRING のような数値を文字列に変換する関数が提供されています。文字列と数値を連結したいなら PRINC-TO-STRING や PRIN1-TO-STRING を以下の例のように使用することができます。
一般的な関数の EQUAL と EQUALP は二つの文字列が等しいかどうかのテストに使用することもできます。文字列は大文字、小文字を区別する方法の EQUAL か、区別しない方法の EQUALP のどちらかで、それぞれの要素ごとに比較されていきます。他にもたくさんの文字列比較に特化した関数があります。実装で定義された文字の属性を活用するならこれらの関数を使いたくなるでしょう。その場合はベンダーのドキュメントをあたってください。
以下にいくつかのサンプルを示してみました。不一致の検査をした全ての関数が、最初のミスマッチが発生した位置で一般的な真偽値を返すことに注意して下さい。もっと多彩な操作を行いたい場合は、一般的な手続き関数の MISMATCH を使用することができます。
SPLIT-SEQUENCE は http://www.cliki.net/SPLIT-SEQUENCE で利用可能な、 Common Lisp のユーティリティーコレクションの一部です。
Copyright © 2002-2007 The Common Lisp Cookbook Project