第68回です。前回はこちら。
[第68回の様子]
2023/02/22に第68回を開催した。
内容としてはRust By Example 日本語版19. 標準ライブラリの型の「19.7. ハッシュマップ」、「19.7.1. key型の変種」、「19.7.2. ハッシュ集合」に取り組んだ。
参加者は自分を入れて6人。最近参加してくれてるほぼフルメンバだった。ありがたや〜。
[学んだこと]
- 19.7. ハッシュマップ
- ベクタ
Vec<T>
はインデックスで値を保持/取得できた - 任意のキーで値を保持/取得するには
HashMap<K, V>
を利用する - ハッシュマップのキーは以下のトレイトを両方実装している必要がある(詳細は19.7.1)
- Eqトレイト
- Hashトレイト
- ベクタと同様、保持する要素の個数を増やす(伸長する)ことができる
- ハッシュマップの場合、余分なスペースを減らすことも可能
- ハッシュマップの初期化はHashMap::with_capacity(usize)とHashMap::new()が利用可能
- 後者が推奨らしい。
- 実際の利用例を見ていく
use std::collections::HashMap; fn call(number: &str) -> &str { match number { "798-1364" => "We're sorry, ...", "645-7689" => "Hello, ...", _ => "Hi! ..." } } fn main() { let mut contacts = HashMap::new(); contacts.insert("Daniel", "798-1364"); contacts.insert("Ashley", "645-7689"); // 参照をとり、Option<&V>を返す。 match contacts.get(&"Daniel") { // Some("798-1364")が返るのでDanielに電話できる // call()の結果"We're sorry, ..."が返る Some(&number) => println!("Calling Daniel: {}", call(number)), _ => println!("Don't have Daniel's number."), } // insert()は同一キーがある場合元の値をラップしたSomeを返す let num = contacts.insert("Daniel", "164-6743"); println!("num={:?}", num); // num=Some("798-1364") // キーを削除する場合はremove() // これもinsert()同様に元の値をラップしたSomeまたはNoneを返す contacts.remove(&"Ashley"); // iter()はキーと値のペアを順不同で返す for (contact, &number) in contacts.iter() { println!("Calling {}: {}", contact, call(number)); // Calling Daniel: Hi! ... } }
- 19.7.1. key型の変種
- keyに利用できるのはEqトレイトとHashトレイトを実装している以下のもの
- bool(2つしかキーがないので非実用的)
- intやuintなど整数型
- Stringと&str
- f32やf64はHashトレイトを実装していないのでキーにならない
- 集合型(コレクション)は要素がキーにできる場合はそれ自体をキーにできる
- 独自の型をkeyとして利用する場合は以下のようにする
// Eqトレイトを使用する時は、PartialEqをderiveする必要がある #[derive(PartialEq, Eq, Hash)] struct Account<'a>{ username: &'a str, password: &'a str, } // 値はHashなどderive不要 struct AccountInfo<'a>{ name: &'a str, email: &'a str, } // Accountをキーとするハッシュマップのエイリアス type Accounts<'a> = HashMap<Account<'a>, AccountInfo<'a>>;
- これを利用すると以下のような疑似ログオン処理になる
use std::collections::HashMap; fn try_logon<'a>(accounts: &Accounts<'a>, username: &'a str, password: &'a str){ println!("Username: {}", username); println!("Password: {}", password); println!("Attempting logon..."); let logon = Account { username, password, }; match accounts.get(&logon) { // 対応するアカウントが見つかった場合 Some(account_info) => { println!("Successful logon!"); println!("Name: {}", account_info.name); println!("Email: {}", account_info.email); }, // 対応するアカウントが見つからない場合 _ => println!("Login failed!"), } } fn main(){ let mut accounts: Accounts = HashMap::new(); let account = Account { username: "j.everyman", password: "password123", }; let account_info = AccountInfo { name: "John Everyman", email: "j.everyman@email.com", }; // j.everymanのアカウントを登録 accounts.insert(account, account_info); // 間違ったパスワードでログオン try_logon(&accounts, "j.everyman", "psasword123"); // Login failed! // 正しいパスワードでログオン try_logon(&accounts, "j.everyman", "password123"); // Successful logon! }
- ライフタイム宣言(
<'a>
)がやはり面倒だよね、という話に。 - ライフタイム宣言が不要なString型を利用したコードに書き換えてみることに。
- 変更のあった部分だけ抜き出すとこのようになった。
#[derive(PartialEq, Eq, Hash)] struct Account { username: String, password: String, } struct AccountInfo { name: String, email: String, } fn try_logon(accounts: &Accounts, username: &str, password: &str) { println!("Username: {}", username); println!("Password: {}", password); println!("Attempting logon..."); let logon = Account { // &strをString型に変換 // 変数名とフィールド名がずれたのでフィールド名が必要に username: String::from(username), password: String::from(password), }; match accounts.get(&logon) { Some(account_info) => { println!("Successful logon!"); println!("Name: {}", account_info.name); println!("Email: {}", account_info.email); } _ => println!("Login failed!"), } } fn main() { let mut accounts: Accounts = HashMap::new(); let account = Account { // to_string()でString型に変換 username: "j.everyman".to_string(), password: "password123".to_string(), }; let account_info = AccountInfo { name: "John Everyman".to_string(), email: "j.everyman@email.com".to_string(), }; accounts.insert(account, account_info); try_logon(&accounts, "j.everyman", "password123"); }
- 毎回Stringオブジェクトをヒープに生成するようになったので、メモリ効率は悪くなってそうだけど、サクッと書くならこっちかなあ...まだまだ修行が足りない
- 19.7.2. ハッシュ集合
- ハッシュ集合、またはHashSet:キーだけのHashMap
- Vecと違って重複がないことが保証されている
- 集合型には他にもunion()、difference()などイテレータを返す便利なメソッドがある
use std::collections::HashSet; fn main() { let mut a: HashSet<i32> = vec![1i32, 2, 3].into_iter().collect(); let mut b: HashSet<i32> = vec![2i32, 3, 4].into_iter().collect(); assert!(a.insert(4)); // 既に存在する値を追加しようとすると // `HashSet::insert()`はfalseを返す。 assert!(b.insert(4) == false); b.insert(5); println!("A: {:?}", a); // A: {1, 4, 2, 3} println!("B: {:?}", b); // B: {5, 4, 2, 3} // union()は重複を排除して全て取得 println!("Union: {:?}", a.union(&b).collect::<Vec<&i32>>()); // Union: [1, 4, 2, 3, 5] // 注:順不同、以下同様 // difference()は引数の集合型に存在しない要素のイテレータを返す println!("Difference: {:?}", a.difference(&b).collect::<Vec<&i32>>()); // Difference: [1] // intersection()は共通する要素のイテレータを返す println!("Intersection: {:?}", a.intersection(&b).collect::<Vec<&i32>>()); // Intersection: [3, 4, 2] // symmetric_difference()はいずれかにしか存在しない要素のイテレータを返す println!("Symmetric Difference: {:?}", a.symmetric_difference(&b).collect::<Vec<&i32>>()); // Symmetric Difference: [1, 5] }
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
メモリ周りきっちりやろうとするとライフタイムとか逃れられない...。
今週のプルリクエストはこちら。