Camomile 使い方メモ (3) 文字列型の比較と操作 [OCaml]
* Camomile のユニコード文字列概観
Camomile でユニコード文字列を扱うモジュールは UText, XString, UTF8, UTF16, UCS4 の5つがあります。
これらの違いとしてはまず
- UTF8, UTF16, UCS4 は immutable (ただし UTF8 は後述を参照)
- XString は mutable
- UText は両方ある (immutable: UText.utext, mutable: UText.ustring, これらは幽霊型で区別されている)
ということがあります。型としては UText.utext, UText.ustring, XString.xstring, UTF8.t, UTF16.t, UCS4.t の6種類になります。
mutable な2種類の文字列 (UText.ustring, XString.xstring) を比べた場合、
- UText.ustring は標準の string 型に近く、サイズの伸縮が不能
- XString モジュールにはサイズの伸縮が可能な関数が用意されている
という違いがあります。
UTF8 に固有の特徴として、UTF8.t は実は UTF-8 エンコーディングのバイト列が入った string 型であり、特に隠蔽もされていないため、標準の String モジュールを使って操作できるということがあげられます。
このため、他の文字列型と比べて外の世界との相互運用性が高いともいえます。
また String モジュールの関数を使うと UTF8 モジュールには提供されていない破壊的操作を行うこともできてしまいます。
また UTF8 と UTF16 は1文字あたりの長さが一意ではないためランダムアクセスの効率は他と比べて悪いはずです。
これ以降では各モジュールに提供されている関数の使い方を見ていきます。出現する例はすべてトップレベルで以下のように open している前提です。
open CamomileLibrary.Default.Camomile;;
種類 | 関数 | UText | XString | UTF8 | UTF16 | UCS4 |
---|---|---|---|---|---|---|
生成 | init | ○ | ○ | ○ | ○ | ○ |
init_ustring | ○ | × | × | × | × | |
of_string | ○ | × | × | × | × | |
make | ○ | ○ | × | × | × | |
copy | ○ | ○ | × | × | × | |
変換 | utext_of_ustring | ○ | × | × | × | × |
ustring_of_utext | ○ | × | × | × | × | |
utext_of | × | ○ | × | × | × | |
ustring_of | × | ○ | × | × | × | |
長さ取得 | length | ○ | ○ | ○ | ○ | ○ |
位置指定アクセス | get | ○ | ○ | ○ | ○ | ○ |
インデクスアクセス | look | ○ | ○ | ○ | ○ | ○ |
nth | ○ | ○ | ○ | ○ | ○ | |
first | ○ | ○ | ○ | ○ | ○ | |
last | ○ | ○ | ○ | ○ | ○ | |
out_of_range | ○ | ○ | ○ | ○ | ○ | |
compare_index | ○ | ○ | ○ | ○ | ○ | |
next | ○ | ○ | ○ | ○ | ○ | |
prev | ○ | ○ | ○ | ○ | ○ | |
move | ○ | ○ | ○ | ○ | ○ | |
イテレーション | iter | ○ | ○ | ○ | ○ | ○ |
比較 | compare | ○ | ○ | ○ | ○ | ○ |
検証 | validate | × | × | ○ | ○ | ○ |
非破壊的操作 | sub | ○ | ○ | × | × | × |
append | ○ | ○ | × | × | × | |
破壊的操作 | set | ○ | ○ | × | × | × |
fill | ○ | × | × | × | × | |
blit | ○ | × | × | × | × | |
clear | × | ○ | × | × | × | |
reset | × | ○ | × | × | × | |
add_char | × | ○ | × | × | × | |
add_text | × | ○ | × | × | × | |
add_xstring | × | ○ | × | × | × | |
shrink | × | ○ | × | × | × |
* 生成
全てのモジュールに共通の文字列生成方法として init 関数があります。
これは標準モジュールの Array.init と同様の高階関数で、引数として「文字の位置を与えられると UChar.t のユニコード文字を返す関数」を与えます。
# UTF8.init 10 (fun pos -> UChar.chr (pos + 0x0030));; - : CamomileLibrary.Default.Camomile.UTF8.t = "0123456789"
UText.ustring の生成には UText.init_ustring を使います。使い方は同じです。
UText モジュールにだけは of_string 関数があり、これは Latin-1 文字列から変換して UText.utext 文字列を作ります。
# UText.of_string "Hello World!";; - : CamomileLibrary.Default.Camomile.UText.utext = <abstr>
mutable な文字列 (UText.ustring, XString.xstring) は make 関数や copy 関数を使って生成することもできます。これは標準モジュールの String.make や String.copy と同様の使い方です。
(* 10文字の A から成る文字列を生成 *) # XString.make 10 (UChar.chr 0x0041);; - : CamomileLibrary.Default.Camomile.XString.xstring = <abstr>
* 変換
UText.utext と UText.ustring は utext_of_ustring と ustring_of_utext を使って相互変換することができます。
また、XString.xstring は XString.utext_of と XString.ustring_of を使ってそれぞれ UText.utext, UText.ustring に変換することができます。
* 長さの取得
全ての文字列型で、文字列長を取得する length 関数が使えます。これはバイト数ではなく文字数を返してくれます。
(* 全角の "0123456789" を生成して文字列長を取得 *) # let s = UTF8.init 10 (fun pos -> UChar.chr (pos + 0x824f));; val s : CamomileLibrary.Default.Camomile.UTF8.t = "\232\137\143\232\137\144\232\137\145\232\137\146\232\137\147\232\137\148\232\137\149\232\137\150\232\137\151\232\137\152" # UTF8.length s;; - : int = 10
* 位置指定のアクセス
全ての文字列型で、指定された位置の文字を返す get 関数が使えます。戻り値は UChar.t 型です。
(* "Hello World!" の6文字目 ('W' = U+0057 = 87) を取得 *) # let ch = UText.get (UText.of_string "Hello World!") 6;; val ch : CamomileLibrary.UChar.t = <abstr> # UChar.code ch;; - : int = 87
* インデクスによるアクセス
全ての文字列型でインデクスによるアクセスが提供されています。インデクスは文字列中の位置をポイントするカーソルのようなもので、進めたり、戻したり、その位置の文字を取得したりできます。インデクス自体は immutable な値で操作すると新しいオブジェクトが返ります。
# let str = UText.of_string "Hello World!";; val str : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # let idx = UText.first str;; val idx : CamomileLibrary.Default.Camomile.UText.index = <abstr> # UChar.code (UText.look str idx);; - : int = 72 # let idx = UText.next str idx;; val idx : CamomileLibrary.Default.Camomile.UText.index = <abstr> # UChar.code (UText.look str idx);; - : int = 101 # let idx = UText.move str idx 5;; val idx : CamomileLibrary.Default.Camomile.UText.index = <abstr> # UChar.code (UText.look str idx);; - : int = 87 # let idx = UText.prev str idx;; val idx : CamomileLibrary.Default.Camomile.UText.index = <abstr> # UChar.code (UText.look str idx);; - : int = 32 # let idx = UText.move str idx 999;; val idx : CamomileLibrary.Default.Camomile.UText.index = <abstr> # UText.out_of_range str idx;; - : bool = true
get 関数と整数の位置情報でも代用できそうですが、文字列型によっては get 関数の実行に文字列長に依存したコストがかかる (UTF8, UTF16) ためインデクスを使用した方がよい場合があるということだと思います。
* イテレーション
全ての文字列型で iter 関数を使うことができます。これは標準モジュールの String.iter と同じで、各文字に対する処理を行うことができる高階関数です。第1引数には「UChar.t を取って何かをする関数」を与えます。
# let str = UText.of_string "Hello World!";; val str : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # UText.iter (fun c -> print_int (UChar.code c); print_newline ()) str;; 72 101 108 108 111 32 87 111 114 108 100 33 - : unit = ()
* 比較
文字列の同値性は compare 関数で比較できます。全ての文字列型で使用できます。
# let str1 = UText.of_string "0123456789";; val str1 : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # let str2 = UText.init 10 (fun pos -> UChar.chr (pos + 0x0030));; val str2 : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # UText.compare str1 str2;; - : int = 0
* 検証
UTF8, UTF16, UCS4 については validate 関数を使って各々のエンコーディングとして正当な表現になっているかの検証を行うことできます。不正だった場合は例外 Malformed_code が投げられます。
# UTF8.validate "\xff";; Exception: UTF8.Malformed_code.
* 非破壊的操作
UText と XString には sub 関数と append 関数が用意されています。標準モジュールの String.sub と String.append と同様に使えます。
(* "0123456789" の4文字目から6文字分を取得 *) # let str1 = UText.of_string "0123456789";; val str1 : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # let str2 = UText.sub str1 3 6;; val str2 : [ `Immutable ] CamomileLibrary.Default.Camomile.UText.text = <abstr> # UText.compare str2 (UText.of_string "345678");; - : int = 0
(* "Hello " と "World!" を連結 *) # let str = UText.of_string "Hello World!";; val str : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # let hello = UText.of_string "Hello ";; val hello : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # let world = UText.of_string "World!";; val world : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # UText.compare str (UText.append hello world);; - : int = 0
* 破壊的操作: set
UText.ustring と XString には set 関数が用意されています。これは標準モジュールの String.set と同じで、指定位置の文字を破壊的に書き換えます。
* 破壊的操作: fill, blit
UText.ustring には fill 関数と blit 関数が用意されています。これは標準モジュールの String.fill, String.blit と同じで、文字列の指定範囲を与えられた文字ないし文字列で書き換えます。
(* "fork" の2文字目から2文字分を '*'=U+002A で置き換える *) # let str1 = UText.ustring_of_utext (UText.of_string "fork");; val str1 : CamomileLibrary.Default.Camomile.UText.ustring = <abstr> # UText.fill str1 1 2 (UChar.chr 0x002a);; - : unit = () # let f2 = UText.ustring_of_utext (UText.of_string "f**k");; val f2 : CamomileLibrary.Default.Camomile.UText.ustring = <abstr> # UText.compare f f2;; - : int = 0
(* "Hello World!" を "Howdy World!" に書き換える *) # let str1 = UText.ustring_of_utext (UText.of_string "Hello World!");; val str1 : CamomileLibrary.Default.Camomile.UText.ustring = <abstr> # let str2 = UText.of_string "Howdy";; val str2 : CamomileLibrary.Default.Camomile.UText.utext = <abstr> # UText.blit str2 0 str1 0 5;; - : unit = () # let str3 = UText.ustring_of_utext (UText.of_string "Howdy World!");; val str3 : CamomileLibrary.Default.Camomile.UText.ustring = <abstr> # UText.compare str1 str3;; - : int = 0
* 破壊的操作: clear, reset, add_char, add_text, add_xstring, shrink
XString だけに存在する関数として clear, reset, add_char, add_text, add_xstring, shrink があります。それぞれ関数名から期待されるような破壊的操作を行います。
# let str = XString.init 10 (fun pos -> UChar.chr (pos + 0x0030));; val str : CamomileLibrary.Default.Camomile.XString.xstring = <abstr> # XString.add_char str (UChar.chr 0x002a);; - : unit = () # XString.length str;; - : int = 11 # XString.add_text str (UText.of_string "ABC");; - : unit = () # XString.length str;; - : int = 14 # XString.shrink str 10;; - : unit = () # XString.length str;; - : int = 10 # XString.clear str;; - : unit = () # XString.length str;; - : int = 0
clear と reset の違いはどうやら clear は内部的には長さ情報を 0 にするだけで reset のほうは既存の内部メモリ領域も捨てて新しくするようです。
コメント 0