文字列操作 with Rust

Rustの文字列操作について書いていきます。随時更新。

# 型
### char
文字の型です。

```rust
    let ch: char = 'a';
    assert_eq!(ch, 'a');
```

### &str
文字列リテラルです。

```rust
    let s: &str = "hello";
    assert_eq!(s, "hello");
```

&がついていることから、通常は変更できません。

### String
文字列の型です。

```rust
    let cs: String = "hello".to_string();
    assert_eq!(cs, String::from("hello"));
```

文字列リテラルの`to_string`で文字列を取得できます。

# 文字列の結合
文字列の結合には、format!マクロが使えます。

```rust
    let hello: String = "hello".to_string();
    let world: String = "world".to_string();
    assert_eq!(String::from("helloworld"), format!("{}{}", hello, world));
```

左側がStringで右側が&strの場合、+演算子でも結合できます。

```rust
    let hello: String = "hello".to_string();
    let world: &str = "world";
    assert_eq!(String::from("helloworld"), hello + world)
```

# 一文字ずつ取得
一文字ずつ取得するには、IteratorのCharsを使います。

```rust
    let cs: String = "hello".to_string();
    for i in cs.as_str().chars() {
        println!("{}", i);
    }
```

```:結果
h
e
l
l
o
```

また、`collect`でVecにもできます。

```rust
    let v: Vec<char> = "hello".chars().collect();
    println!("{}", v[0]);
```

```:結果
h
```

`nth`も使えます。

```rust
    let v: char = "hello".chars().nth(0).unwrap();
    println!("{}", v);
```

```:結果
h
```

Iteratorの`next`で一文字進めることができます。

```rust
    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の末尾に追加できます。

```rust
    let mut cs: String = "hello".to_string();
    cs.push(',');
    cs.push_str(" world");
    assert_eq!(String::from("hello, world"), cs);
```

# 文字列を逆順にする
`rev`を使って文字列を逆順にします。

```rust
    let s: &str = "hello";
    assert_eq!("olleh", s.chars().rev().collect::<String>());
```

# 文字を削除
`pop`と`remove`でStringから文字を削除できます。
`pop`は、最後の文字を削除し、Optionを返します。
Stringが空だった場合、Noneを返します。

```rust
    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`は、指定した位置の文字を削除して返します。

```rust
    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の指定された文字だけを保持できます。

```rust
    let mut cs: String = "h_e_l_l_o".to_string();
    cs.retain(|c| c != '_');
    assert_eq!(String::from("hello"), cs);
```

# 文字列の大きさ
`len`で取得できます。

```rust
    let cs: String = "hello".to_string();
    assert_eq!(5, cs.len());
```

# 文字列の繰り返し
`repeat`を使うと文字列を繰り返すことができます。

```rust
    let cs: String = "hello".to_string();
    assert_eq!(String::from("hellohellohello"), cs.repeat(3));
```

# 大文字、小文字
小文字にしたいときは`to_lowercase`、大文字にしたいときは`to_uppercase`を使います。

```rust
    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`は数を指定しての置き換えです。

```rust
    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`はパターンに一致する文字で区切ります。

```rust
    let v: Vec<&str> = "hello,world,goodbye,world".split(',').collect();
    assert_eq!(v, ["hello", "world", "goodbye", "world"]);
```

`rsplit`は`split`の逆順で区切っていきます。

```rust
    let v: Vec<&str> = "hello,world,goodbye,world".rsplit(',').collect();
    assert_eq!(v, ["world", "goodbye", "world", "hello"]);
```

空白で分割するときは、`split_whitespace`が便利です。

```rust
    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`が使えます。

```rust
    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`の逆順です。

```rust
    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`で先頭と末尾の文字列を判定できます。

```rust
    let s: &str = "hello";
    assert!(s.starts_with("he"));
    assert!(s.ends_with("lo"));
```

# 検索
`find`と`rfind`で検索できます。

```rust
    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`を使って数字だけを抽出します。

```rust
    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本ノック](http://www.cl.ecei.tohoku.ac.jp/nlp100/)を少し解いていきます。

00
文字列"stressed"の文字を逆に（末尾から先頭に向かって）並べた文字列を得よ．

```rust
    let s: String = "stressed".chars().rev().collect::<String>();
```
01
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ．

```rust
    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."という文を単語に分解し，各単語の（アルファベットの）文字数を先頭から出現順に並べたリストを作成せよ．

```rust
    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文字を取り出し，取り出した文字列から単語の位置（先頭から何番目の単語か）への連想配列（辞書型もしくはマップ型）を作成せよ．

```rust
    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);
            }
        }
    }
```