Rustコレクションと文字列

コレクション(Collection)はデータ構造の中で最も一般的なデータ格納形式です。Rust標準ライブラリには、開発者がデータ構造の操作を処理するための豊富なコレクション型が用意されています。

ベクトル

ベクトル(Vector)は複数の値を格納する単一のデータ構造であり、同じ型の値をメモリ内に線形に格納します。

ベクトルは線形リストであり、Rustでは Vec<T> で表されます。

ベクトルの使用方法はリスト(List)に似ており、次の方法で指定した型のベクトルを作成できます:

let vector: Vec<i32> = Vec::new(); // 创建类型为 i32 的空向量
let vector = vec![1, 2, 4, 8];     // 通过数组创建向量

線形リストを使用する際、追加操作をよく行いますが、追加とスタックのpush操作は本質的に同じです。そのため、ベクトルには単一要素を追加するpushメソッドのみがあります:

を取得しようとすると、ベクトルはエラーを返します。

fn main() {
    let mut vector = vec![1, 2, 4, 8];
    vector.push(16);
    vector.push(32);
    vector.push(64);
    println!("{:?}", vector);
}

実行結果:

[1, 2, 4, 8, 16, 32, 64]

appendメソッドは、あるベクトルを別のベクトルの末尾に連結するために使用します:

インスタンス

fn main() {
    let mut v1: Vec<i32> = vec![1, 2, 4, 8];
    let mut v2: Vec<i32> = vec![16, 32, 64];
    v1.append(&mut v2);
    println!("{:?}", v1);
}

実行結果:

[1, 2, 4, 8, 16, 32, 64]

getメソッドはベクトル内の値を取得するために使用します:

インスタンス

fn main() {
    let mut v = vec![1, 2, 4, 8];
    println!("{}", match v.get(0) {
        Some(value) => value.to_string(),
        None => "None".to_string()
    });
}

実行結果:

1

ベクトルの長さは論理的に推測できないため、getメソッドは必ず値を取得できるとは限りません。そのため、getメソッドの戻り値はOption列挙型であり、空の可能性があります。

これは安全な値の取得方法ですが、記述がやや面倒です。取得する添え字がベクトルの添え字の範囲を超えないことを保証できる場合は、配列の値取得構文を使用することもできます:

インスタンス

fn main() {
    let v = vec![1, 2, 4, 8];
    println!("{}", v[1]);
}

実行結果:

2

しかし、v

ベクトルの走査:

インスタンス

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
            println!("{}", i);
    }
}

実行結果:

100
32
57

走査中に変数の値を変更する必要がある場合:

インスタンス

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

文字列

文字列クラス(String)はこの章までに多く使用されてきたため、多くのメソッドは読者にすでに熟知されています。この章では主に文字列のメソッドとUTF-8の性質を紹介します。

新しい文字列の作成:

let string = String::new();

基本型を文字列に変換:

let one = 1.to_string();         // 整数到字符串
let float = 1.3.to_string();     // 浮点数到字符串
let slice = "slice".to_string(); // 字符串切片到字符串

UTF-8文字を含む文字列:

let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");

文字列の追加:

let mut s = String::from("run");
s.push_str("oob"); // 追加字符串切片
s.push('!');       // 追加字符

+ 記号で文字列を連結:

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;

この構文には文字列スライスも含めることができます:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = s1 + "-" + &s2 + "-" + &s3;

format! マクロを使用:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);

文字列の長さ:

let s = "hello";
let len = s.len();

ここでのlenの値は5です。

let s = "你好";
let len = s.len();

ここでのlenの値は6です。中国語はUTF-8エンコーディングであり、各文字は3バイトの長さなので、長さは6になります。しかし、RustはUTF-8文字オブジェクトをサポートしているため、文字数を数えたい場合は、まず文字列を文字コレクションとして取得できます:

let s = "hello你好";
let len = s.chars().count();

ここでのlenの値は7です。全部で7文字あるためです。文字数を数える速度は、データの長さを数える速度よりもはるかに遅いです。

文字列の走査:

インスタンス

fn main() {
    let s = String::from("hello中文");
    for c in s.chars() {
        println!("{}", c);
    }
}

実行結果:

h
e
l
l
o
中
文

文字列から単一の文字を取得:

インスタンス

fn main() {
    let s = String::from("EN中文");
    let a = s.chars().nth(2);
    println!("{:?}", a);
}

実行結果:

Some('中')

注意:nth関数はイテレータから値を取得するメソッドです。走査中にこのように使用しないでください!UTF-8では各文字の長さが必ずしも等しいとは限らないためです!

文字列の部分文字列を切り出したい場合:

インスタンス

fn main() {
    let s = String::from("EN中文");
    let sub = &s[0..2];
    println!("{}", sub);
}

実行結果:

EN

ただし、この使用方法はUTF-8文字を分解する可能性があることに注意してください!その場合はエラーが発生します:

インスタンス

fn main() {
    let s = String::from("EN中文");
    let sub = &s[0..3];
    println!("{}", sub);
}

実行結果:

thread 'main' panicked at 'byte index 3 is not a char boundary; it is inside '中' (bytes 2..5) of `EN中文`', src\libcore\str\mod.rs:2069:5 
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

マップ

マップ(Map)は他の言語でも広く存在します。その中で最も一般的に使用されているのはキーと値のハッシュマップ(Hash Map)です。

新しいハッシュマップを作成:

インスタンス

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    println!("{}", map.get("color").unwrap());
}

注意:ここでハッシュマップのジェネリクスを宣言していないのは、Rustの自動型推論メカニズムによるものです。

実行結果:

red

insertメソッドとgetメソッドは、マップで最も一般的に使用される2つのメソッドです。

マップはイテレータをサポートしています:

インスタンス

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    for p in map.iter() {
        println!("{:?}", p);
    }
}

実行結果:

("color", "red") 
("size", "10 m^2")

反復要素はキーと値のペアを表すタプルです。

Rustのマップは非常に便利なデータ構造です。insertメソッドで新しいキーと値のペアを追加するとき、同じキーがすでに存在する場合は対応する値を直接上書きします。「安全に挿入」したい場合、つまり現在そのキーが存在しないことを確認してから挿入アクションを実行する場合は、次のようにします:

map.entry("color").or_insert("red");

この文の意味は、キーが "color" のキーと値のペアがなければそれを追加して値を "red" に設定し、そうでなければスキップするということです。

特定のキーが存在することが確実な場合に、対応する値を直接変更したい場合は、より速い方法があります:

インスタンス

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, "a");
   
    if let Some(x) = map.get_mut(&1) {
        *x = "b";
    }
}
その他の拡張