From 542d4e23751edf0f33086a520cf07adf3d4af4c1 Mon Sep 17 00:00:00 2001 From: Daniel Faust Date: Sun, 26 Mar 2017 11:41:53 +0200 Subject: [PATCH 1/4] Add multimap! macro --- src/lib.rs | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index aa06631..52f2835 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,8 +18,9 @@ //! and memory penalty. Notably, indexing by `K2` requires two HashMap lookups. //! //! ``` -//! # extern crate multi_map; +//! extern crate multi_map; //! use multi_map::MultiMap; +//! //! # fn main() { //! #[derive(Hash,Clone,PartialEq,Eq)] //! enum ThingIndex { @@ -27,9 +28,11 @@ //! IndexTwo, //! IndexThree, //! }; +//! //! let mut map = MultiMap::new(); //! map.insert("One", ThingIndex::IndexOne, 1); //! map.insert("Two", ThingIndex::IndexTwo, 2); +//! //! assert!(*map.get_alt(&ThingIndex::IndexOne).unwrap() == 1); //! assert!(*map.get(&"Two").unwrap() == 2); //! assert!(map.remove_alt(&ThingIndex::IndexTwo).unwrap() == 2); @@ -64,6 +67,16 @@ impl MultiMap { } } + /// Creates an empty MultiMap with the specified capacity. + /// + /// The multi map will be able to hold at least `capacity` elements without reallocating. If `capacity` is 0, the multi map will not allocate. + pub fn with_capacity(capacity: usize) -> MultiMap { + MultiMap { + value_map: HashMap::with_capacity(capacity), + key_map: HashMap::with_capacity(capacity), + } + } + /// Insert an item into the MultiMap. You must supply both keys to insert /// an item. The keys cannot be modified at a later date, so if you only /// have one key at this time, use a placeholder value for the second key @@ -157,6 +170,50 @@ impl MultiMap { } } +#[macro_export] +/// Create a MultiMap from a list of key-value tuples +/// +/// ## Example +/// +/// ``` +/// #[macro_use] +/// extern crate multi_map; +/// use multi_map::MultiMap; +/// +/// # fn main() { +/// #[derive(Hash,Clone,PartialEq,Eq)] +/// enum ThingIndex { +/// IndexOne, +/// IndexTwo, +/// IndexThree, +/// }; +/// +/// let map = multimap!{ +/// "One", ThingIndex::IndexOne => 1, +/// "Two", ThingIndex::IndexTwo => 2, +/// }; +/// +/// assert!(*map.get_alt(&ThingIndex::IndexOne).unwrap() == 1); +/// assert!(*map.get(&"Two").unwrap() == 2); +/// # } +/// ``` +macro_rules! multimap { + (@single $($x:tt)*) => (()); + (@count $($rest:expr),*) => (<[()]>::len(&[$(multimap!(@single $rest)),*])); + + ($($key1:expr, $key2:expr => $value:expr,)+) => { multimap!($($key1, $key2 => $value),+) }; + ($($key1:expr, $key2:expr => $value:expr),*) => { + { + let _cap = multimap!(@count $($key1),*); + let mut _map = MultiMap::with_capacity(_cap); + $( + _map.insert($key1, $key2, $value); + )* + _map + } + }; +} + mod test { #[test] @@ -195,6 +252,32 @@ mod test { assert!(*map.get(&2).unwrap() == String::from("Zwei!")); assert!(map.get_alt(&"Three") == None); assert!(map.get(&3) == None); + } + + #[test] + fn macro_test() { + use ::MultiMap; + + let _: MultiMap = multimap!{}; + + multimap!{ + 1, "One" => String::from("Eins"), + }; + + multimap!{ + 1, "One" => String::from("Eins") + }; + + multimap!{ + 1, "One" => String::from("Eins"), + 2, "Two" => String::from("Zwei"), + 3, "Three" => String::from("Drei"), + }; + multimap!{ + 1, "One" => String::from("Eins"), + 2, "Two" => String::from("Zwei"), + 3, "Three" => String::from("Drei") + }; } } From 2b9f51008406171765d7de949a80938078bfa0ec Mon Sep 17 00:00:00 2001 From: Daniel Faust Date: Sun, 26 Mar 2017 11:44:38 +0200 Subject: [PATCH 2/4] Fix clippy warnings --- src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 52f2835..2da8704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! # multi-map //! -//! MultiMap is like a std::collection::HashMap, but allows you to use either of +//! `MultiMap` is like a `std::collection::HashMap`, but allows you to use either of //! two different keys to retrieve items. //! //! The keys have two distinct types - `K1` and `K2` - which may be the same. @@ -8,14 +8,14 @@ //! `remove_alt` methods, while accessing via the secondary `K2` key is via new //! `get_alt`, `get_mut_alt` and `remove_alt` methods. The value is of type `V`. //! -//! Internally, two HashMaps are created - a main one on `` and a second one on ``. The `(K2, V)` tuple is so //! that when an item is removed using the `K1` key, the appropriate `K2` //! value is available so the `K2->K1` map can be removed from the second -//! HashMap, to keep them in sync. +//! `MultiMap`, to keep them in sync. //! -//! Using two HashMaps instead of one naturally brings a slight performance -//! and memory penalty. Notably, indexing by `K2` requires two HashMap lookups. +//! Using two `HashMap`s instead of one naturally brings a slight performance +//! and memory penalty. Notably, indexing by `K2` requires two `HashMap` lookups. //! //! ``` //! extern crate multi_map; @@ -112,7 +112,7 @@ impl MultiMap { pub fn get_alt(&self, key: &K2) -> Option<&V> { let mut result = None; if let Some(key_a) = self.key_map.get(key) { - if let Some(pair) = self.value_map.get(&key_a) { + if let Some(pair) = self.value_map.get(key_a) { result = Some(&pair.1) } } @@ -124,7 +124,7 @@ impl MultiMap { pub fn get_mut_alt(&mut self, key: &K2) -> Option<&mut V> { let mut result = None; if let Some(key_a) = self.key_map.get(key) { - if let Some(pair) = self.value_map.get_mut(&key_a) { + if let Some(pair) = self.value_map.get_mut(key_a) { result = Some(&mut pair.1) } } @@ -171,7 +171,7 @@ impl MultiMap { } #[macro_export] -/// Create a MultiMap from a list of key-value tuples +/// Create a `MultiMap` from a list of key-value tuples /// /// ## Example /// From c7317b272468a108efe20b8ee43a8788ccf4ea5e Mon Sep 17 00:00:00 2001 From: Daniel Faust Date: Sun, 26 Mar 2017 11:48:37 +0200 Subject: [PATCH 3/4] Add crates.io and docs.rs links to readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57f6b70..cc72b1e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Multi-Map [![Build Status](https://travis-ci.org/thejpster/multi-map.svg?branch=master)](https://travis-ci.org/thejpster/multi-map) +[![Crate version](https://img.shields.io/crates/v/multi-map.svg)](https://crates.io/crates/multi-map) +[![Documentation](https://img.shields.io/badge/documentation-docs.rs-df3600.svg)](https://docs.rs/multi-map) -Like a std::collection::HashMap, but allows you to use either of two different keys to retrieve items. +Like a `std::collection::HashMap`, but allows you to use either of two different keys to retrieve items. For more details, see [the Github.io page](http://thejpster.github.io/multi-map/). - From b62ff854a6ff4205512dc6cdc0fcaf38d5c33c27 Mon Sep 17 00:00:00 2001 From: Daniel Faust Date: Sun, 26 Mar 2017 18:46:16 +0200 Subject: [PATCH 4/4] Implement PartialEq, Eq and Debug --- src/lib.rs | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2da8704..0914736 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,9 @@ use std::collections::HashMap; use std::collections::hash_map::Iter; use std::hash::Hash; use std::borrow::Borrow; +use std::fmt::{self, Debug}; +#[derive(Eq)] pub struct MultiMap { value_map: HashMap, key_map: HashMap, @@ -170,6 +172,18 @@ impl MultiMap { } } +impl PartialEq for MultiMap { + fn eq(&self, other: &MultiMap) -> bool { + self.value_map.eq(&other.value_map) + } +} + +impl fmt::Debug for MultiMap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_map().entries(self.value_map.iter().map(|(key_one, &(ref key_two, ref value))| ((key_one, key_two), value))).finish() + } +} + #[macro_export] /// Create a `MultiMap` from a list of key-value tuples /// @@ -258,26 +272,36 @@ mod test { fn macro_test() { use ::MultiMap; - let _: MultiMap = multimap!{}; + let map: MultiMap = MultiMap::new(); + + assert_eq!(map, multimap!{}); - multimap!{ + let mut map = MultiMap::new(); + map.insert(1, "One", String::from("Eins")); + + assert_eq!(map, multimap!{ 1, "One" => String::from("Eins"), - }; + }); - multimap!{ + assert_eq!(map, multimap!{ 1, "One" => String::from("Eins") - }; + }); + + let mut map = MultiMap::new(); + map.insert(1, "One", String::from("Eins")); + map.insert(2, "Two", String::from("Zwei")); + map.insert(3, "Three", String::from("Drei")); - multimap!{ + assert_eq!(map, multimap!{ 1, "One" => String::from("Eins"), 2, "Two" => String::from("Zwei"), 3, "Three" => String::from("Drei"), - }; + }); - multimap!{ + assert_eq!(map, multimap!{ 1, "One" => String::from("Eins"), 2, "Two" => String::from("Zwei"), 3, "Three" => String::from("Drei") - }; + }); } }