在開發程式時,我們常會使用HashMap來儲存key-value結構的資料。但很多時候我們的key-value資料會有不只一層,例如一個男女分班的學校,我們就需要有性別、班級、學號這三層的key-value結構,以確保各層的資料不會有重複的情況發生。或是可以分成國家、一級行政區、二級行政區等多層資料的世界地圖。
Leveled HashMap
「Leveled HashMap」是筆者開發的套件,提供了LeveledHashMap
結構體,用來處理階層式的key-value資料,LeveledHashMap
結構體的每一層都是一個HashMap結構實體,並非是每個key-value條目底下都自帶一個HashMap結構實體,因此可以確保每層的key皆不會相同。如此一來,在搜尋時可以直接指定要從哪個層級開始搜尋,效率極高。
Crates.io
Cargo.toml
使用方法
使用use
關鍵字將leveled_hash_map
這個crate下的LeveledHashMap
結構體給引用到當前的程式範圍下。LeveledHashMap
結構體的new
關聯函數可以建立出LeveledHashMap
結構體的實體。
use leveled_hash_map::LeveledHashMap;
let map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
以下介紹LeveledHashMap
結構實體的增、刪、查、改之方式。
新增
LeveledHashMap
結構實體提供了insert
方法,可以將一個值,新增到一個Key鏈下。例如:
use std::sync::Arc;
use leveled_hash_map::LeveledHashMap;
let mut map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
map.insert(&[Arc::new("food")], "食物".to_string()).unwrap();
map.insert(&[Arc::new("food"), Arc::new("dessert")], "甜點".to_string()).unwrap();
如果該Key鏈已經有存在的值,就會被回傳出來。
如果想要在同一層下,新增多個值,可以使用LeveledHashMap
結構實體提供的insert_many
方法,直接將一個HashMap
結構實體新增至LeveledHashMap
結構實體中的某個Key鏈的子Key中。例如:
use std::sync::Arc;
use std::collections::HashMap;
use leveled_hash_map::LeveledHashMap;
let mut map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
map.insert(&[Arc::new("food")], "食物".to_string()).unwrap();
let mut insert_map = HashMap::new();
insert_map.insert("dessert", "甜點".to_string());
insert_map.insert("meat", "肉類".to_string());
map.insert_many(&[Arc::new("food")], insert_map, 0).unwrap();
如果該Key鏈的子Key中已經有存在的值,就會被回傳出來。
刪除
LeveledHashMap
結構實體提供了remove
方法,可以將一個值從一個Key鏈上移除。例如:
use std::sync::Arc;
use leveled_hash_map::LeveledHashMap;
let mut map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
map.insert(&[Arc::new("food")], "食物".to_string()).unwrap();
map.insert(&[Arc::new("food"), Arc::new("dessert")], "甜點".to_string()).unwrap();
map.insert(&[Arc::new("food"), Arc::new("meat")], "肉類".to_string()).unwrap();
let result = map.remove(&[Arc::new("food"), Arc::new("dessert")]).unwrap();
assert_eq!("甜點", result.0);
assert_eq!(0, result.1.len());
let result = map.remove(&[Arc::new("food")]).unwrap();
assert_eq!("食物", result.0);
assert_eq!(1, result.1.len());
assert_eq!(&(Some(Arc::new("food")), "肉類".to_string()), result.1[0].get(&Arc::new("meat")).unwrap());
如果該Key鏈上確實有值,就會將該值和該Key鏈下所有層級的子Key和值回傳出來。
也可以使用LeveledHashMap
結構實體提供的remove_advanced
方法,來直接從某個層級開始,移除指定的Key鏈。可以省下檢查前面幾層Key鏈正確性的時間。例如:
use std::sync::Arc;
use leveled_hash_map::LeveledHashMap;
let mut map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
map.insert(&[Arc::new("food")], "食物".to_string()).unwrap();
map.insert(&[Arc::new("food"), Arc::new("dessert")], "甜點".to_string()).unwrap();
map.insert(&[Arc::new("food"), Arc::new("meat")], "肉類".to_string()).unwrap();
let result = map.remove_advanced(&[Arc::new("dessert")], 1).unwrap();
assert_eq!("甜點", result.0);
assert_eq!(0, result.1.len());
let result = map.remove_advanced(&[Arc::new("food")], 0).unwrap();
assert_eq!("食物", result.0);
assert_eq!(1, result.1.len());
assert_eq!(&(Some(Arc::new("food")), "肉類".to_string()), result.1[0].get(&Arc::new("meat")).unwrap());
查詢
LeveledHashMap
結構實體提供了get
方法,可以取得一個Key鏈上之值的不可變參考。例如:
use std::sync::Arc;
use leveled_hash_map::LeveledHashMap;
let mut map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
map.insert(&[Arc::new("food")], "食物".to_string()).unwrap();
map.insert(&[Arc::new("food"), Arc::new("dessert")], "甜點".to_string()).unwrap();
let desert = map.get(&[Arc::new("food"), Arc::new("dessert")]);
也可以使用LeveledHashMap
結構實體提供的get_advanced
方法,來直接從某個層級開始,取得指定Key鏈的值的不可變參考。可以省下檢查前面幾層Key鏈正確性的時間。例如:
use std::sync::Arc;
use leveled_hash_map::LeveledHashMap;
let mut map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
map.insert(&[Arc::new("food")], "食物".to_string()).unwrap();
map.insert(&[Arc::new("food"), Arc::new("dessert")], "甜點".to_string()).unwrap();
let desert = map.get_advanced(&[Arc::new("dessert")], 1);
如果要取得某指定層級的HashMap結構實體的(不可變)參考,可以使用LeveledHashMap
結構實體提供的keys
方法。例如:
use std::sync::Arc;
use std::collections::HashMap;
use leveled_hash_map::LeveledHashMap;
let mut map: LeveledHashMap<&'static str, String> = LeveledHashMap::new();
map.insert(&[Arc::new("food")], "食物".to_string()).unwrap();
let mut insert_map = HashMap::new();
insert_map.insert("dessert", "甜點".to_string());
insert_map.insert("meat", "肉類".to_string());
map.insert_many(&[Arc::new("food")], insert_map, 0).unwrap();
let result = map.keys(0).unwrap();
assert_eq!(1, result.len());
let result = map.keys(1).unwrap();
assert_eq!(2, result.len());
修改
LeveledHashMap
結構實體提供的各類get
方法,方法名稱後面皆可以加上_mut
,來取得值的可變參考,如此就可以直接改變LeveledHashMap
結構實體內的值。
使用案例
以下簡單建立一個擁有多國語言名稱的抽象「世界地圖」:
use std::sync::Arc;
use std::collections::HashMap;
use leveled_hash_map::LeveledHashMap;
#[derive(Debug)]
struct MultiName {
us: &'static str,
tw: &'static str,
cn: &'static str,
}
fn main() {
let earth_map: LeveledHashMap<&'static str, MultiName> = {
let mut map = LeveledHashMap::new();
map.insert(&[Arc::new("US")], MultiName {
us: "United States of America",
tw: "美國",
cn: "美国",
}).unwrap();
let mut us_states = HashMap::new();
us_states.insert("New York", MultiName {
us: "New York",
tw: "紐約州",
cn: "纽约州",
});
us_states.insert("Utah", MultiName {
us: "Utah",
tw: "猶他州",
cn: "犹他州",
});
map.insert_many(&[Arc::new("US")], us_states, 0).unwrap();
map.insert(&[Arc::new("CN")], MultiName {
us: "China",
tw: "中國",
cn: "中国",
}).unwrap();
let mut cn_provinces = HashMap::new();
cn_provinces.insert("Guangdong", MultiName {
us: "Guangdong",
tw: "廣東省",
cn: "广东省",
});
cn_provinces.insert("Fujian", MultiName {
us: "Fujian",
tw: "福建省",
cn: "福建省",
});
map.insert_many(&[Arc::new("CN")], cn_provinces, 0).unwrap();
map.insert(&[Arc::new("TW")], MultiName {
us: "Taiwan",
tw: "臺灣",
cn: "臺湾",
}).unwrap();
let mut tw_counties = HashMap::new();
tw_counties.insert("Taipei", MultiName {
us: "Taipei",
tw: "台北",
cn: "台北",
});
tw_counties.insert("Taichung", MultiName {
us: "Taichung",
tw: "台中",
cn: "台中",
});
map.insert_many(&[Arc::new("TW")], tw_counties, 0).unwrap();
map
};
println!("{:?}", earth_map);
let countries = earth_map.keys(0).unwrap();
println!("{:?}", countries); // {"CN": {"Fujian", "Guangdong"}, "US": {"Utah", "New York"}, "TW": {"Taichung", "Taipei"}}
let new_york = earth_map.get(&[Arc::new("US"), Arc::new("New York")]).unwrap();
println!("{:?}", new_york); // MultiName { us: "New York", tw: "紐約州", cn: "纽约州" }
let new_york = earth_map.get_advanced(&[Arc::new("New York")], 1).unwrap();
println!("{:?}", new_york); // MultiName { us: "New York", tw: "紐約州", cn: "纽约州" }
let new_york_suspicion = earth_map.get(&[Arc::new("TW"), Arc::new("New York")]);
println!("{:?}", new_york_suspicion); // None
}