Rustの文字列操作について書いていきます。随時更新。
型
char
文字の型です。
let ch: char = 'a';
assert_eq!(ch, 'a');
&str
文字列リテラルです。
let s: &str = "hello";
assert_eq!(s, "hello");
&がついていることから、通常は変更できません。
String
文字列の型です。
let cs: String = "hello".to_string();
assert_eq!(cs, String::from("hello"));
文字列リテラルのto_string
で文字列を取得できます。
文字列の結合
文字列の結合には、format!マクロが使えます。
let hello: String = "hello".to_string();
let world: String = "world".to_string();
assert_eq!(String::from("helloworld"), format!("{}{}", hello, world));
左側がStringで右側が&strの場合、+演算子でも結合できます。
let hello: String = "hello".to_string();
let world: &str = "world";
assert_eq!(String::from("helloworld"), hello + world)
一文字ずつ取得
一文字ずつ取得するには、IteratorのCharsを使います。
let cs: String = "hello".to_string();
for i in cs.as_str().chars() {
println!("{}", i);
}
h
e
l
l
o
また、collect
でVecにもできます。
let v: Vec<char> = "hello".chars().collect();
println!("{}", v[0]);
h
nth
も使えます。
let v: char = "hello".chars().nth(0).unwrap();
println!("{}", v);
h
Iteratorのnext
で一文字進めることができます。
let s: &str = "hello";
let mut chs = s.chars();
assert_eq!(Some('h'), chs.next());
assert_eq!(Some('e'), chs.next());
assert_eq!(Some('l'), chs.next());
assert_eq!(Some('l'), chs.next());
assert_eq!(Some('o'), chs.next());
そのまま表示したい場合は、unwrap
で値を取り出してください。
末尾に追加
pushでchar、push_strで&strをそれぞれStringの末尾に追加できます。
let mut cs: String = "hello".to_string();
cs.push(',');
cs.push_str(" world");
assert_eq!(String::from("hello, world"), cs);
文字列を逆順にする
rev
を使って文字列を逆順にします。
let s: &str = "hello";
assert_eq!("olleh", s.chars().rev().collect::<String>());
文字を削除
pop
とremove
でStringから文字を削除できます。
pop
は、最後の文字を削除し、Optionを返します。
Stringが空だった場合、Noneを返します。
let mut cs: String = "hello".to_string();
assert_eq!(Some('o'), cs.pop());
assert_eq!(Some('l'), cs.pop());
assert_eq!(Some('l'), cs.pop());
assert_eq!(String::from("he"), cs);
remove
は、指定した位置の文字を削除して返します。
let mut cs: String = "hello".to_string();
assert_eq!('o', cs.remove(4));
assert_eq!('h', cs.remove(0));
assert_eq!(String::from("ell"), cs);
特定の文字を排除する
retain
でStringの指定された文字だけを保持できます。
let mut cs: String = "h_e_l_l_o".to_string();
cs.retain(|c| c != '_');
assert_eq!(String::from("hello"), cs);
文字列の大きさ
len
で取得できます。
let cs: String = "hello".to_string();
assert_eq!(5, cs.len());
文字列の繰り返し
repeat
を使うと文字列を繰り返すことができます。
let cs: String = "hello".to_string();
assert_eq!(String::from("hellohellohello"), cs.repeat(3));
大文字、小文字
小文字にしたいときはto_lowercase
、大文字にしたいときはto_uppercase
を使います。
let cs: String = "hElLo".to_string();
assert_eq!(String::from("HELLO"), cs.to_uppercase());
assert_eq!(String::from("hello"), cs.to_lowercase());
置き換え
replace
とreplacen
で置き換えができます。
replace
は通常の置き換えで、replacen
は数を指定しての置き換えです。
let s: &str = "foo foo foo";
assert_eq!("faa faa faa", s.replace("o", "a"));
assert_eq!("fee fee foo", s.replacen("o", "e", 4));
分割
文字列の分割にはsplit
とrsplit
が使えます。
split
はパターンに一致する文字で区切ります。
let v: Vec<&str> = "hello,world,goodbye,world".split(',').collect();
assert_eq!(v, ["hello", "world", "goodbye", "world"]);
rsplit
はsplit
の逆順で区切っていきます。
let v: Vec<&str> = "hello,world,goodbye,world".rsplit(',').collect();
assert_eq!(v, ["world", "goodbye", "world", "hello"]);
空白で分割するときは、split_whitespace
が便利です。
let mut spw = "a b c d".split_whitespace();
assert_eq!(Some("a"), spw.next());
assert_eq!(Some("b"), spw.next());
assert_eq!(Some("c"), spw.next());
assert_eq!(Some("d"), spw.next());
数を指定しての分割は、splitn
とrsplitn
で行います。
パターンを抽出する
matches
とrmatches
が使えます。
let v: Vec<&str> = "aaaOOOaaaQQQaaa".matches("aaa").collect();
assert_eq!(v, ["aaa", "aaa", "aaa"]);
let nv: Vec<&str> = "1aaa2bbb3ccc4".matches(char::is_numeric).collect();
assert_eq!(nv, ["1", "2", "3", "4"]);
rmacthes
はmatches
の逆順です。
let v: Vec<&str> = "aaaOOOaaaQQQaaa".rmatches("aaa").collect();
assert_eq!(v, ["aaa", "aaa", "aaa"]);
let nv: Vec<&str> = "1aaa2bbb3ccc4".rmatches(char::is_numeric).collect();
assert_eq!(nv, ["4", "3", "2", "1"]);
先頭の文字列と末尾の文字列のパターン
starts_with
とends_with
で先頭と末尾の文字列を判定できます。
let s: &str = "hello";
assert!(s.starts_with("he"));
assert!(s.ends_with("lo"));
検索
find
とrfind
で検索できます。
let s: &str = "hello, world";
assert_eq!(Some(0), s.find('h'));
assert_eq!(Some(7), s.find("world"));
assert_eq!(Some(6), s.find(char::is_whitespace));
rfindはfindの逆順です。
数字だけを抽出する
split_whitespace
とfilter_map
を使って数字だけを抽出します。
let s: &str = "1 2 a 3 b 4 aaa 5 l 10";
let cs: Vec<i32> = s.split_whitespace().filter_map(|k| k.parse().ok()).collect::<Vec<i32>>();
assert_eq!(cs, [1, 2, 3, 4, 5, 10]);
split_whitespace
で区切り、filter_map
で数字へのパースが成功したもののみをcollect
しています。
おまけ 言語処理100本ノック
言語処理100本ノックを少し解いていきます。
00
文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
let s: String = "stressed".chars().rev().collect::<String>();
01
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.
let cs: String = "パタトクカシーー".chars().step_by(2).collect::<String>();
02
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.
let s: &str = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.";
let pi: Vec<usize> = s.split_whitespace()
.map(|st| st.chars().filter(|ch| ch.is_alphabetic()).collect::<String>().len())
.collect::<Vec<usize>>();
03
"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭に2文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.
let s: &str = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.";
let cs: Vec<String> = s.split_whitespace()
.map(|st| st.chars().filter(|ch| ch.is_alphabetic()).collect::<String>())
.collect::<Vec<String>>();
let mut elem = HashMap::new();
for (c, j) in cs.into_iter().enumerate() {
match c {
0 | 4...8 | 14 | 15 | 18 => {
elem.insert(j.get(..1).unwrap().to_string(), c);
},
_ => {
elem.insert(j.get(..2).unwrap().to_string(), c);
}
}
}