Rustで配列内の特定の値のみ処理する様々な方法
ここでは様々な方法を駆使して配列内の特定の値を処理する様々な方法が書かれています。
ここではfilter
メソッドとfind
メソッドを使ったやり方で、
特定の値を抽出する方法について説明していきます。
find
メソッドを使用したやり方は、趣味でRustのBevyでゲーム開発をしている中で見つけたもので、
filter
メソッドはこの記事を公開した時に記事を見てくれた方から教えてもらったものです。
それぞれのメソッドの書き方や、違いなども説明していきますのでよろしくお願いします。
まずはデータを定義
まずは検証を行うためのデータを定義します。
今回は人のデータPerson
構造体と、性別のタイプGender
列挙型を定義していきます。
derive
には比較するためのPartialEq
、コンソールに出力するためのDebug
を追加します。
あとは値をわかりやすく定義するためにnew
メソッドを追加しています。
new
メソッドを追加することで、Person::new(name, age, gender)
の形式で
値を定義することができるようになります。
#[derive(PartialEq, Debug)]
struct Person {
name: String,
age: usize,
gender: Gender,
}
#[derive(PartialEq, Debug)]
enum Gender {
Male,
Female,
}
impl Person {
fn new(name: &str, age: usize, gender: Gender) -> Self {
let name = name.to_string();
Person {
name,
age,
gender,
}
}
}
次にmain
関数内で値を定義していきましょう!
以下のコードでは複数のPerson
を定義し、その値を配列の変数に入れてまとめています。
fn main() {
// まずはデータを定義
let john = Person::new("John", 20, Gender::Male);
let marry = Person::new("Marry", 21, Gender::Female);
let bob = Person::new("Bob", 18, Gender::Male);
let carol = Person::new("Carol", 24, Gender::Female);
let person_list = [john, marry, bob, carol];
}
これで準備はできました!次は実践していきます!
for文を使った処理
説明の前にfor
文での処理を紹介します。
悪い例ではないのですが、これから紹介する実装方法に比べて、少し分かりにくいものになっています。
以下のコードでは配列をfor
文でループして一番上に「条件に合わない値を飛ばす」処理を追加して
「条件に合った値を出力する」処理が書かれています。
このコードでも動作はします。「値をループさせて特定の値でなければ飛ばして、特定の値なら出力する」 みたいにパッと見て処理の内容がわかって分かりやすいのですが、条件式が分かりづらく感じます。
fn main() {
// ...
forloop(&person_list);
}
fn forloop(person_list: &[Person; 4]) {
for (index, person) in person_list.iter().enumerate() {
if person.gender != Gender::Male {
continue;
}
println!("index: {}", index);
println!("person: {:?}", person);
}
}
filterメソッドを使った処理
ではfilter
メソッドを使用した処理を見ていきましょう。
このコードは上記のコードと同じ結果になります。
ここではPerson
のデータが入った配列の変数をループさせ、
filter
メソッドで条件に合った値のみを抽出し、その値のみを処理するといった流れになります。
以下のコードはfor
文の例よりも、見やすく分かりやすいように見えます。
特に条件文がわかりやすくなったことで「どのような値を抽出するのか」が
パッと見てわかるようになったと思います。
fn main() {
// ...
loopfilter(&person_list);
}
fn loopfilter(person_list: &[Person; 4]) {
person_list
.iter()
.enumerate()
.filter(|(_index, person)| person.gender == Gender::Male)
.for_each(|(index, person)| {
println!("index: {}", index);
println!("person: {:?}", person);
});
}
findメソッドを使った処理
次にfind
メソッドを使用した処理を見ていきましょう。
このコードは上記のコードと少し異なる結果になります。
ここではPerson
のデータが入った配列の変数をループさせ、
find
メソッドを使って値を取り出し、その値をSome((index, person))
に代入します。
そして代入された値はfind
メソッド下の鉤括弧の中で処理されます。
結果が少し異なるといった点は、find
メソッドで条件に合った最初の値のみ処理が実行される点です。
つまりループをしてfind
メソッドで条件に合った値が見つかった場合、
それ以降にループされる値がもしも条件に合っていても処理が実行されず無視されます。
つまり最初の値のみ処理されると言うことはSome((index, person))
に代入される値は1つと言うことになります。
find
メソッドのメリットとしてはループされる値の中で、
条件に合った1つだけの値を取得したい時などに良いです。
fn main() {
// ...
ifletfind(&person_list);
}
fn ifletfind(person_list: &[Person; 4]) {
if let Some((index, person)) = person_list
.iter()
.enumerate()
.find(|(_, person_value)| person_value.gender == Gender::Male)
{
println!("index: {}", index);
println!("person: {:?}", person);
}
}
if letとは
find
メソッドの使用例にif let
を使いましたのでここで簡単な説明をしています。
if let
の説明は「Rust By Example 日本語版」で見ることができます。
公式が言うには、match
式を使うほど大掛かりな処理ではなく、
小さな処理を書く時にmatch
式だと不自然な書き方になってしまう。
その際にif let
を使うと綺麗に書くことができるよと書かれています。
つまり、以下のコードのようにnumber
が1だったら鉤括弧の中の処理を実行するよと言うことです。
もしもnumber
が1以外だったら処理は実行されないと言うことですね。
number
が1以外の時の何か処理を書いておきたいときも、元はif
文なのでelse
を使うこともできるみたいです。
let number = 1;
if let 1 = number {
println!("number is 1");
} else {
println!("number is not 1");
}
コード全容
今回作成したRustのコードは以下のようになります。
#[derive(PartialEq, Debug)]
struct Person {
name: String,
age: usize,
gender: Gender,
}
#[derive(PartialEq, Debug)]
enum Gender {
Male,
Female,
}
impl Person {
fn new(name: &str, age: usize, gender: Gender) -> Self {
let name = name.to_string();
Person {
name,
age,
gender,
}
}
}
fn main() {
// まずはデータを定義
let john = Person::new("John", 20, Gender::Male);
let marry = Person::new("Marry", 21, Gender::Female);
let bob = Person::new("Bob", 18, Gender::Male);
let carol = Person::new("Carol", 24, Gender::Female);
let person_list = [john, marry, bob, carol];
forloop(&person_list);
loopfilter(&person_list);
ifletfind(&person_list);
}
fn forloop(person_list: &[Person; 4]) {
for (index, person) in person_list.iter().enumerate() {
if person.gender != Gender::Male {
continue;
}
println!("index: {}", index);
println!("person: {:?}", person);
}
}
fn loopfilter(person_list: &[Person; 4]) {
person_list
.iter()
.enumerate()
.filter(|(_index, person)| person.gender == Gender::Male)
.for_each(|(index, person)| {
println!("index: {}", index);
println!("person: {:?}", person);
});
}
fn ifletfind(person_list: &[Person; 4]) {
if let Some((index, person)) = person_list
.iter()
.enumerate()
.find(|(_, person_value)| person_value.gender == Gender::Male)
{
println!("index: {}", index);
println!("person: {:?}", person);
}
}
結果は以下のようになります。
$ cargo run
#
# index: 0
# person: Person { name: "John", age: 20, gender: Male }
# index: 2
# person: Person { name: "Bob", age: 18, gender: Male }
# index: 0
# person: Person { name: "John", age: 20, gender: Male }
# index: 2
# person: Person { name: "Bob", age: 18, gender: Male }
# index: 0
# person: Person { name: "John", age: 20, gender: Male }
まとめ
今回は配列内の特定の値のみ処理する様々な方法について説明してみました。
find
メソッドを使用した例でif let
の使い方が分からずその使用を避けていましたが、
ゲーム開発時に使える場面が出てきてこれは使えるぞ!と感動しました。
この感動をバネにこの記事を書いたわけですが、本当に便利な機能だと思います。
filter
メソッドは記事のコメントで教えてもらった事で、このコードも使えるぞ!と言うことで
記事にfilter
メソッドの実装方法を追加して更新しました。
もしこの記事が何かの参考になれたら幸いです。 ここまで読んでいただきありがとうございました!