Rustの基礎 - 集合
概要
Rustにおける集合とは、HashMapの値の無いキーだけの集まりのようなものである。
複数の集合に共通の要素があるのか、片方にあって片方に無いものは何かといった計算をすることができる。
HashSetは標準ライブラリに含まれているが、使用する場合には std::collections::HashSet をインポートする必要がある。
集合の宣言
HashSetを使用するには、std::collections::HashSet をインポートする必要がある。
集合の要素には、同じ値が複数存在せず、ただ1つだけである。
したがって、複数の同じ値を集合の要素に追加しても要素は整理される。
use std::collections::HashSet;
// 空のHashSetを作成
let mut set_a = HashSet::new();
set_a.insert(1);
set_a.insert(3);
set_a.insert(5);
set_a.insert(7);
set_a.insert(9);
// 重複を含むHashSetを作成
let mut set_b = HashSet::new();
set_b.insert(1);
set_b.insert(1);
set_b.insert(1);
set_b.insert(3);
set_b.insert(5);
set_b.insert(7);
set_b.insert(8);
set_b.insert(5);
set_b.insert(6);
set_b.insert(6);
set_b.insert(3);
set_b.insert(3);
set_b.insert(9);
set_b.insert(7);
println!("{:?}", set_b);
// 出力
{1, 3, 5, 6, 7, 8, 9}
また、空のHashSetは new メソッドで生成する。
from メソッドを使用すると、配列から直接HashSetを生成することもできる。
use std::collections::HashSet;
// 空のHashSetを作成
let set_a: HashSet<i32> = HashSet::new();
// 配列からHashSetを作成
let set_b = HashSet::from([1, 2, 3, 4, 5]);
println!("{:?}", set_b);
// 出力
{1, 2, 3, 4, 5}
※注意
HashSet型には、要素の順番が存在しない。
そのため、複数の要素が含まれている場合、どのような順序で並んでいるのかは不定である。
また、HashSet型には同じ値の要素は1つしか格納できないため、同じ値を持つ要素は1つにまとめられる。
以下の例では、集合の要素として、"H" "a" "p" "p" "y"という5つの文字を要素として集合を作成している。
重複した"p"は1つにまとめられ、順序は不定である。
use std::collections::HashSet;
let mut my_set = HashSet::new();
my_set.insert("H");
my_set.insert("a");
my_set.insert("p");
my_set.insert("p");
my_set.insert("y");
println!("{:?}", my_set);
// 出力(順序は不定)
{"H", "y", "a", "p"}
集合のメソッド
要素の追加
集合に要素を加えるには、insert メソッドを使用する。
insert メソッドは、要素が追加された場合はtrueを返し、既に存在していた場合はfalseを返す。
use std::collections::HashSet;
let mut x = HashSet::new();
x.insert(10);
x.insert("Rust");
println!("{:?}", x);
// 出力(順序は不定)
{10, "Rust"}
要素の削除
また、集合の要素を削除するには、remove メソッドを使用する。
remove メソッドは、要素が削除された場合はtrueを返し、要素が存在しなかった場合はfalseを返す。
集合の要素を全て削除するには、clear メソッドを使用する。
use std::collections::HashSet;
let mut x = HashSet::from([3, 1, 5, 4, 2]);
x.remove(&5);
println!("{:?}", x);
x.clear();
println!("{:?}", x);
// 出力 (順序は不定)
{1, 2, 3, 4}
{}
removeメソッドは、要素が存在しない場合でもパニックを起こさず、falseを返すだけである。
これは、Pythonのdiscard関数と同様の動作である。
use std::collections::HashSet;
let mut color_set = HashSet::from(["Red", "Green", "Blue"]);
// 存在しない要素を削除しようとしてもエラーにならない
color_set.remove("White");
println!("{:?}", color_set);
// 出力 (順序は不定)
{"Blue", "Green", "Red"}
要素の取得
HashSetから任意の要素を1つ取り出すための専用メソッドは存在しない。
iter メソッドを使用してイテレータを取得して、next メソッドで要素を取得することができる。
ただし、どの要素が取得されるかは不定である。
use std::collections::HashSet;
let mut color_set = HashSet::from(["Red", "Green", "Blue"]);
// イテレータから要素を1つ取得
if let Some(&color) = color_set.iter().next() {
println!("{}", color);
color_set.remove(color);
println!("{:?}", color_set);
}
if let Some(&color) = color_set.iter().next() {
println!("{}", color);
color_set.remove(color);
println!("{:?}", color_set);
}
// 出力 (順序は不定)
Green
{"Blue", "Red"}
Blue
{"Red"}
要素数の取得
len メソッドは、HashSetに含まれる要素の数を取得することができる。
use std::collections::HashSet;
let set1 = HashSet::from(["Red", "Green", "Blue"]);
let set2 = HashSet::from([1, 2, 3, 4, 5]);
println!("{}", set1.len());
println!("{}", set2.len());
// 出力
3
5
イテレータを使用して集合に変換する
collect メソッドを使用すれば、配列、ベクター、文字列等のイテレータを持つコレクションから重複する要素を取り除いてHashSetを生成することができる。
以下の例では、重複した要素は整理されている。
文字列の場合は、文字ごとに分解されてHashSetに格納される。
use std::collections::HashSet;
// 文字列から作成 (文字ごとに分解される)
let a: HashSet<char> = "RustRust".chars().collect();
// ベクターから作成
let b: HashSet<&str> = vec!["Rust", "JavaScript", "PHP", "Rust", "JavaScript", "Rust"]
.into_iter()
.collect();
// 配列から作成
let c: HashSet<&str> = ["Rust", "JavaScript", "PHP", "C", "JavaScript", "Ruby", "Swift"]
.into_iter()
.collect();
// HashMapのキーからHashSetを作成
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("x", 100);
map.insert("y", 200);
map.insert("z", 300);
let d: HashSet<_> = map.keys().cloned().collect();
println!("{:?}", a);
println!("{:?}", b);
println!("{:?}", c);
println!("{:?}", d);
// 出力 (順序は不定)
{'R', 'u', 's', 't'}
{"Rust", "JavaScript", "PHP"}
{"Rust", "JavaScript", "PHP", "C", "Ruby", "Swift"}
{"x", "y", "z"}
配列からHashSetを生成するには、from メソッド または collect メソッドを使用する。
use std::collections::HashSet;
let my_array = ["A", "B", "C"]; // 配列の生成
// fromメソッドを使用
let my_set1 = HashSet::from(my_array);
// collectメソッドを使用
let my_set2: HashSet<_> = my_array.into_iter().collect();
println!("{:?}", my_set1);
println!("{:?}", my_set2);
// 出力 (順序は不定)
{"B", "C", "A"}
{"B", "C", "A"}
ベクターからHashSetを作成するには、collect メソッドを使用する。
use std::collections::HashSet;
let my_vec = vec!["A", "B", "C"]; // ベクターの生成
let my_set: HashSet<_> = my_vec.into_iter().collect();
println!("{:?}", my_set);
// 出力 (順序は不定)
{"B", "C", "A"}
範囲 (Range) からHashSetを生成するには、collect メソッドを使用する。
Rustの範囲 (Range) とは、開始値から終了値までの連続した数値を表すイテレータである。
範囲は、開始値..終了値 という形式で記述する。
終了値は範囲に含まれない。
開始値..=終了値 という形式では、終了値も範囲に含まれる。
use std::collections::HashSet;
// 0〜9までの範囲からHashSetを作成
let my_set: HashSet<_> = (0..10).collect();
println!("{:?}", my_set);
// 出力 (順序は不定)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
集合の演算
積集合
積集合は、両方の集合に共通する要素からなる集合である。
& 演算子 または intersection メソッドを使用して求めることができる。
intersection メソッドはイテレータを返すため、collect メソッドで新しいHashSetに変換する必要がある。
use std::collections::HashSet;
let a = HashSet::from([1, 2, 3]);
let b = HashSet::from([2, 3, 4]);
// &演算子を使用
let c = &a & &b;
// intersectionメソッドを使用
let d: HashSet<_> = a.intersection(&b).cloned().collect();
println!("{:?}", c);
println!("{:?}", d);
// 出力(順序は不定)
{2, 3}
{2, 3}
和集合
和集合は、少なくともどちらかの集合に要素が含まれている集合である。
| 演算子 または union メソッドを使用して求めることができる。
use std::collections::HashSet;
let a = HashSet::from([1, 2, 3]);
let b = HashSet::from([2, 3, 4]);
// |演算子を使用
let c = &a | &b;
// union()メソッドを使用
let d: HashSet<_> = a.union(&b).cloned().collect();
println!("{:?}", c);
println!("{:?}", d);
// 出力(順序は不定)
{1, 2, 3, 4}
{1, 2, 3, 4}
差集合
差集合は、一方には含まれているがもう一方には含まれていない要素の集合である。
- 演算子 または difference メソッドで求めることができる。
use std::collections::HashSet;
let a = HashSet::from([1, 2, 3]);
let b = HashSet::from([2, 3, 4]);
// -演算子を使用
let c = &a - &b;
// differenceメソッドを使用
let d: HashSet<_> = a.difference(&b).cloned().collect();
println!("{:?}", c);
println!("{:?}", d);
// 出力 (順序は不定)
{1}
{1}
XOR (対称差集合)
XORは、どちらか片方に含まれるが両方には含まれない要素の集合である。
^ 演算子 または symmetric_difference メソッドで求めることができる。
use std::collections::HashSet;
let a = HashSet::from([1, 2, 3]);
let b = HashSet::from([2, 3, 4]);
// ^演算子を使用
let c = &a ^ &b;
// symmetric_differenceメソッドを使用
let d: HashSet<_> = a.symmetric_difference(&b).cloned().collect();
println!("{:?}", c);
println!("{:?}", d);
// 出力 (順序は不定)
{1, 4}
{1, 4}
集合と他の集合との関係
集合と集合が等しいかどうか、また、集合が他の集合の部分集合かどうか等、集合と他の集合との関係を確認する。
集合が他の集合と等しいかどうか
比較演算子の == または != で比較することができる。
集合aの要素が集合bに全て含まれており、集合bの要素が集合aに全て含まれている場合、trueとなる。
use std::collections::HashSet;
let set1 = HashSet::from(["A", "B", "C"]);
let set2 = HashSet::from(["B", "C", "A"]);
println!("{}", set1 == set2);
// 出力
true
部分集合
is_subset メソッドで比較することができる。
集合aの要素が全て集合bに含まれている場合、集合aは集合bの部分集合であるという。
以下の例では、集合set1は集合set2の部分集合であるが、集合set2は集合set1の部分集合ではない。
集合set1と集合set3が等しい場合においても、集合set1は集合set3の部分集合となる。
use std::collections::HashSet;
let set1 = HashSet::from(["A", "B"]);
let set2 = HashSet::from(["B", "D", "C", "A"]);
let set3 = HashSet::from(["B", "A"]);
println!("{}", set1.is_subset(&set2)); // set1がset2の部分集合の場合、true
println!("{}", set2.is_subset(&set1));
println!("{}", set1.is_subset(&set3));
// 出力
true
false
true
真部分集合
Rustには真部分集合を直接判定するメソッドは存在しないが、is_subset メソッド と == 演算子を組み合わせることで判定することができる。
集合aの要素が全て集合bに含まれており、かつ、集合aと集合bが等しくない場合、集合aは集合bの真部分集合であるという。
以下の例では、集合set1は集合set2の真部分集合であり、集合set1と集合set3は真部分集合ではない。
use std::collections::HashSet;
let set1 = HashSet::from(["A", "B"]);
let set2 = HashSet::from(["B", "D", "C", "A"]);
let set3 = HashSet::from(["A", "B"]);
// 真部分集合の判定: 部分集合であり、かつ等しくない
println!("{}", set1.is_subset(&set2) && set1 != set2);
println!("{}", set2.is_subset(&set1) && set2 != set1);
println!("{}", set1.is_subset(&set3) && set1 != set3);
// 出力
true
false
false
超集合
is_superset メソッドで比較することができる。
集合bの要素が全て集合aに含まれている場合、集合aは集合bの超集合であるという。
以下の例では、集合set1は集合set2の超集合ではないが、集合set2は集合set1の超集合である。
集合set1と集合set3が等しい場合も、集合set1は集合set3の超集合となる。
use std::collections::HashSet;
let set1 = HashSet::from(["A", "B"]);
let set2 = HashSet::from(["B", "D", "C", "A"]);
let set3 = HashSet::from(["B", "A"]);
println!("{}", set1.is_superset(&set2));
println!("{}", set2.is_superset(&set1));
println!("{}", set1.is_superset(&set3));
println!("{}", set3.is_superset(&set1));
// 出力
false
true
true
true
真超集合
Rustには真超集合を直接判定するメソッドは存在しないが、is_superset メソッド と == 演算子を組み合わせることで判定することができる。
集合bの要素が全て集合aに含まれており、かつ、集合aと集合bが等しくない場合、集合aは集合bの真超集合であるという。
以下の例では、集合set1は集合set2の真超集合ではないが、集合set2は集合set1の真超集合である。
また、集合set1と集合set3が等しい場合も真超集合ではない。
use std::collections::HashSet;
let set1 = HashSet::from(["A", "B"]);
let set2 = HashSet::from(["B", "D", "C", "A"]);
let set3 = HashSet::from(["A", "B"]);
// 真超集合の判定: 超集合であり、かつ等しくない
println!("{}", set1.is_superset(&set2) && set1 != set2);
println!("{}", set2.is_superset(&set1) && set2 != set1);
println!("{}", set1.is_superset(&set3) && set1 != set3);
// 出力
false
true
false
互いに素
is_disjoint メソッドで比較することができる。
集合aと集合bが同じ要素を1つも持たない時、集合aは集合bと互いに素であるという。
以下の例では、集合set1は集合set2は共通の要素がないので互いに素であるが、
集合set1と集合set3、および、集合set2と集合set3は共通する要素があるため互いに素ではない。
use std::collections::HashSet;
let set1 = HashSet::from(["A", "B"]);
let set2 = HashSet::from(["C", "D", "E"]);
let set3 = HashSet::from(["A", "C"]);
println!("{}", set1.is_disjoint(&set2));
println!("{}", set1.is_disjoint(&set3));
println!("{}", set2.is_disjoint(&set3));
// 出力
true
false
false
集合に指定した値と同じ要素が含まれているか確認する
集合に対して、指定した値と同じ要素が含まれているかどうか確認することもできる。
要素が含まれているかどうかを確認するには、contains メソッドを使用する。
contains メソッドは、集合の要素に指定した値と同じ値の要素が存在する場合はtrue、存在しない場合はfalseとなる。
否定形を使用したい場合は、! 演算子を使用して、containsメソッドの結果を反転させる。
use std::collections::HashSet;
let my_set = HashSet::from(["A", "B", "C"]);
println!("{}", my_set.contains("A"));
println!("{}", my_set.contains("D"));
println!("{}", !my_set.contains("D"));
// 出力
true
false
true