文字列を数えたい
プログラムを書いているとある文字列が何文字であるのか数えたくなる瞬間がある。今回はたまたまRustでそのようなシチュエーションに遭遇したとする。
取り合えず以下のようなプログラムを考える
fn main() {
let value = "ハローワールド";
}
value
変数に入っている&str
の長さが知りたいとする。
雑にエディタのサジェスチョンを見ると&str
型にlen()
関数があるのが分かる
というわけでコードを以下のようにしてみる
fn main() {
let value = "ハローワールド";
let len = value.len();
assert_eq!(7, len);
}
直感的には通りそうだがこれは実行時エラーとなる
len
len
変数の中身を確かめてみると21
となってる。なぜか。
&str.len(&self)
のドキュメントを読んでみると以下のようになっている。
self
の長さを返す
ここでいう長さとはバイト数であり、文字数ではない。いいかえれば、人間の考える文字列の長さではない場合がある。
str - Rustより筆者訳
len
は文字列の長さではなくバイト長を返すらしい。
日本語、というかASCIIでない文字はバイト数が1ではないため返り値と文字列の長さが一致しない。emojiなども同様である🤔
実際にハローワールド
をutf-8
に直すと以下のようになり、21バイトである。
\xe3\x83\x8f\xe3\x83\xad\xe3\x83\xbc\xe3\x83\xaf\xe3\x83\xbc\xe3\x83\xab\xe3\x83\x89
では、文字数を数えるにはどうすればよいのか
chars, count
文字数を数えたい場合には一般的に以下のようなコードを書く
fn main() {
let value = "ハローワールド";
let len = value.chars().count();
assert_eq!(7, len);
}
chars()
は文字列スライスの、1文字に関するイテレータを返す
str - Rustより筆者訳
count()
はイテレータの長さを返すので事実上文字列の長さとなる。
しかしドキュメントにはこのような記載もある。
char
はUnicodeのスカラー値を表し、あなたの考える文字とは一致しない可能性があることが重要だ。あなたが実際に欲しいのは、書記素クラスタに対する反復処理かもしれない。この機能はRustの標準では提供されていないのでcrate.ioをチェックしてほしい
str - Rustより筆者訳
書記素クラスタとは
書記素クラスタとは、人間が1文字として認識する最小単位であるらしい。
Unicodeでは以下のように求められると定まっている。
https://unicode.org/reports/tr29/
Rustで書記素クラスタを扱う場合はunicode-segmentationを使用するのが一般的なように見える。(筆者の観測範囲内で)
ドキュメントのexampleによればこのように使用できるようだ
use unicode_segmentation::UnicodeSegmentation;
fn main() {
let s = "a̐éö̲\r\n";
let g = s.graphemes(true).collect::<Vec<&str>>();
let b: &[_] = &["a̐", "é", "ö̲", "\r\n"];
assert_eq!(g, b);
let s = "The quick (\"brown\") fox can't jump 32.3 feet, right?";
let w = s.unicode_words().collect::<Vec<&str>>();
let b: &[_] = &["The", "quick", "brown", "fox", "can't", "jump", "32.3", "feet", "right"];
assert_eq!(w, b);
let s = "The quick (\"brown\") fox";
let w = s.split_word_bounds().collect::<Vec<&str>>();
let b: &[_] = &["The", " ", "quick", " ", "(", "\"", "brown", "\"", ")", " ", "fox"];
assert_eq!(w, b);
}
unicode-segmentation - crates.io: Rust Package Registryより引用
まとめ
-
len
はバイトの長さを返す -
chars().count()
は文字数を返す - 書記素クラスタを考慮する場合はcrateを使う