Rustの文字列からn文字目の文字charを取得
Rustで文字列(&str
, String
)からn
文字目の文字char
を取得する方法について説明する。
文字ではなく部分文字列を取得したい場合は以下の記事を参照。
- 関連記事: Rustで文字列の位置を指定して部分文字列を取得
以下のサンプルコードでは、説明のため、型推論によって省略できるときも明示的に型注釈を記述している場合がある。また、簡単のためunwrap
を使っている。必要であればその他のメソッドやmatch
などで処理すればよい。
文字列からn文字目の文字を取得: chars().nth()
&str
やString
は、ベクタVec
や配列のように[n]
でインデックス指定できない。
let s: &str = "😀👍abc";
// let c = s[1];
// error[E0277]: the type `str` cannot be indexed by `{integer}`
// note: you can use `.chars().nth()` or `.bytes().nth()`
コンパイラが教えてくれるように、.chars().nth()
または.bytes().nth()
を使う。
chars
は文字char
のイテレータ、bytes
はバイトu8
のイテレータを返す。
nth
はイテレータのメソッドで、n
番目の要素をOption
で返す。要素数を超える位置を指定するとNone
を返す。
したがって、n
番目の文字char
は以下のように取得できる。
let c: char = s.chars().nth(1).unwrap();
assert_eq!(c, '👍');
assert_eq!(s.chars().nth(100), None);
例は省略するが、文字列String
でも同様にchars
メソッドが使用可能。
書記素クラスタを考慮: unicode-segmentation
char
は書記素クラスタを考慮しないため、chars
では国旗の絵文字などが2文字に分割されてしまう。
It’s important to remember that
char
represents a Unicode Scalar Value, and might not match your idea of what a ‘character’ is. Iteration over grapheme clusters may be what you actually want. str::chars - Rust
let s: &str = "JAPAN🇯🇵";
assert_eq!(
s.chars().collect::<Vec<char>>(),
['J', 'A', 'P', 'A', 'N', '🇯', '🇵']
);
let c: char = s.chars().nth(5).unwrap();
assert_eq!(c, '🇯');
書記素クラスタを考慮するにはunicode-segmentationを使う。
UnicodeSegmentation
トレイトをインポートすると、&str
やString
から書記素のイテレータを返すgraphemes
メソッドが使えるようになる。
use unicode_segmentation::UnicodeSegmentation;
let s: &str = "JAPAN🇯🇵";
assert_eq!(
s.graphemes(true).collect::<Vec<&str>>(),
["J", "A", "P", "A", "N", "🇯🇵"]
);
let c_str: &str = s.graphemes(true).nth(5).unwrap();
assert_eq!(c_str, "🇯🇵");
let c_str: &str = s.graphemes(true).nth(2).unwrap();
assert_eq!(c_str, "P");
文字char
ではなく文字列スライス&str
が返されるので注意。
なお、graphemes
メソッドのis_extended
引数はtrue
にすることが推奨されている。
If is_extended is true, the iterator is over the extended grapheme clusters; otherwise, the iterator is over the legacy grapheme clusters. UAX#29 recommends extended grapheme cluster boundaries for general processing. unicode_segmentation::UnicodeSegmentation::graphemes - Rust
例は省略するが、文字列String
からも同様にgraphemes
メソッドが使える。
1バイト文字(ASCII文字)だけの場合: as_bytes().get()
文字列に含まれるのが1バイト文字(ASCII文字)だけの場合、そのままバイトu8
として処理することもできる。
上述のように、コンパイラのメッセージでは.bytes().nth()
が推奨されているが、Clippyでは.as_bytes().get()
が推奨されている。
bytes
はバイトのイテレータを返すのに対し、as_bytes
はバイトのスライス&[u8]
を返す。
さらに、[]
だと範囲外の位置を指定した場合、実行時にパニックになるが、get
はOption
を返し、範囲外だとNone
となる。
let s: &str = "xyz<123>";
let b: &u8 = s.as_bytes().get(1).unwrap();
assert_eq!(*b, 121);
assert_eq!(*b as char, 'y');
assert_eq!(s.as_bytes().get(100), None);
上の例における121
(= 0x79
)はy
のASCIIコード。get
が返すのは&u8
で、char
に変換するときなどは*
を付ける必要があるので注意。
例は省略するが、文字列String
からも同様にas_bytes
メソッドが使える。
同じ文字列から繰り返し文字を取得する場合
同じ文字列から複数回に渡って文字を取得する場合を考える。
上述のように1バイト文字だけの場合はas_bytes
でバイトのスライス&[u8]
に変換できるので、それを繰り返し利用できる。
let s: &str = "xyz<123>";
let bytes: &[u8] = s.as_bytes();
assert_eq!(*bytes.get(2).unwrap() as char, 'z');
assert_eq!(*bytes.get(5).unwrap() as char, '2');
chars().nth()
やgraphemes().nth()
の場合、繰り返すと毎回イテレータを生成して最初から反復することになる。特に気にならない程度であれば問題ないが、より効率的には以下の2通りの方法も考えられる。
イテレータを再利用
chars
やgraphemes
が返すイテレータを保持して再利用する。ただし、nth
はイテレータを進めていくので、先頭から順に文字を取得していく必要がある。前に戻ることはできない
let s: &str = "😀👍abc";
let mut chars = s.chars();
let pos1 = 1;
let pos2 = 3;
assert_eq!(chars.nth(pos1).unwrap(), '👍');
assert_eq!(chars.nth(pos2 - pos1 - 1).unwrap(), 'b');
例はchars
だがgraphemes
でも同様。graphemes
の場合、char
ではなく&str
が返されるので注意。
collectでベクタに変換
collect
でベクタVec
に変換する方法もある。
ベクタに変換してしまえば、[]
やget
で要素を取得できる。
let s: &str = "😀👍abc";
let v: Vec<char> = s.chars().collect();
assert_eq!(v[1], '👍');
assert_eq!(v[3], 'b');
assert_eq!(*v.get(1).unwrap(), '👍');
assert_eq!(*v.get(3).unwrap(), 'b');
assert_eq!(v.get(100), None);
イテレータのように、前から順に文字を取得しなければならないという制約はない。ただし、新たにベクタを生成するので、その分のメモリが余計に確保されるというデメリットがある。
例はchars
だがgraphemes
でも同様。graphemes
の場合はVec<&str>
に変換する。