diff --git a/Cargo.lock b/Cargo.lock index 8ca6e8355cc..873c56022e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.14.1" @@ -1073,6 +1075,10 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +[[package]] +name = "litemap" +version = "0.1.0" + [[package]] name = "log" version = "0.4.11" diff --git a/Cargo.toml b/Cargo.toml index 9199d8bdf22..1a539c37382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "tools/benchmark/macros", "tools/benchmark/memory", "utils/fixed_decimal", + "utils/litemap", "utils/writeable", ] diff --git a/utils/litemap/Cargo.toml b/utils/litemap/Cargo.toml new file mode 100644 index 00000000000..2ee09ab229e --- /dev/null +++ b/utils/litemap/Cargo.toml @@ -0,0 +1,18 @@ +# This file is part of ICU4X. For terms of use, please see the file +# called LICENSE at the top level of the ICU4X source tree +# (online at: https://github.com/unicode-org/icu4x/blob/master/LICENSE ). + +[package] +name = "litemap" +version = "0.1.0" +authors = ["The ICU4X Project Developers"] +repository = "https://github.com/unicode-org/icu4x" +edition = "2018" +license-file = "../../LICENSE" +keywords = ["sorted", "vec", "map", "hashmap", "btreemap"] +description = "A key-value Map implementation based on a flat, sorted Vec." +documentation = "https://docs.rs/litemap" +readme = "./README.md" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/utils/litemap/README.md b/utils/litemap/README.md new file mode 100644 index 00000000000..f35c16236ef --- /dev/null +++ b/utils/litemap/README.md @@ -0,0 +1,15 @@ +## `litemap` + +`litemap` is a crate providing LiteMap, a highly simplistic "flat" key-value map +based off of a single sorted vector. + +The goal of this crate is to provide a map that is good enough for small +sizes, and does not carry the binary size impact of [`HashMap`] +or [`BTreeMap`]. + +If binary size is not a concern, [`BTreeMap`] may be a better choice +for your use case. It behaves very similarly to `LiteMap` for less than 12 elements, +and upgrades itself gracefully for larger inputs. + + [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html + [`BTreeMap`]: https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html \ No newline at end of file diff --git a/utils/litemap/src/lib.rs b/utils/litemap/src/lib.rs new file mode 100644 index 00000000000..da2aab7317f --- /dev/null +++ b/utils/litemap/src/lib.rs @@ -0,0 +1,21 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/master/LICENSE ). + +//! ## `litemap` +//! +//! `litemap` is a crate providing [`LiteMap`], a highly simplistic "flat" key-value map +//! based off of a single sorted vector. +//! +//! The goal of this crate is to provide a map that is good enough for small +//! sizes, and does not carry the binary size impact of [`std::collections::HashMap`] +//! or [`std::collections::BTreeMap`]. +//! +//! If binary size is not a concern, [`std::collections::BTreeMap`] may be a better choice +//! for your use case. It behaves very similarly to [`LiteMap`] for less than 12 elements, +//! and upgrades itself gracefully for larger inputs. +//! + +mod map; + +pub use map::LiteMap; diff --git a/utils/litemap/src/map.rs b/utils/litemap/src/map.rs new file mode 100644 index 00000000000..f7d850d1703 --- /dev/null +++ b/utils/litemap/src/map.rs @@ -0,0 +1,221 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/master/LICENSE ). + +use std::borrow::Borrow; +use std::mem; +use std::ops::{Index, IndexMut}; + +/// A simple "flat" map based on a sorted vector +/// +/// See the [module level documentation][super] for why one should use this. +/// +/// The API is roughly similar to that of [`std::collections::HashMap`], though it +/// requires `Ord` instead of `Hash`. +#[derive(Clone, Debug)] +pub struct LiteMap { + values: Vec<(K, V)>, +} + +impl LiteMap { + /// Construct a new [`LiteMap`] + pub fn new() -> Self { + Self::default() + } + + /// The number of elements in the [`LiteMap`] + pub fn len(&self) -> usize { + self.values.len() + } + + /// Remove all elements from the [`LiteMap`] + pub fn clear(&mut self) { + self.values.clear() + } + + /// Reserve capacity for `additional` more elements to be inserted into + /// the [`LiteMap`] to avoid frequent reallocations. + /// + /// See [`Vec::reserve()`] for more information. + pub fn reserve(&mut self, additional: usize) { + self.values.reserve(additional) + } +} + +impl LiteMap { + /// Get the value associated with `key`, if it exists. + /// + /// ```rust + /// use litemap::LiteMap; + /// + /// let mut map = LiteMap::new(); + /// map.insert(1, "one"); + /// map.insert(2, "two"); + /// assert_eq!(map.get(&1), Some(&"one")); + /// assert_eq!(map.get(&3), None); + /// ``` + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Ord, + { + match self.values.binary_search_by(|k| k.0.borrow().cmp(&key)) { + Ok(found) => Some(&self.values[found].1), + Err(_) => None, + } + } + + /// Returns whether `key` is contained in this map + /// + /// ```rust + /// use litemap::LiteMap; + /// + /// let mut map = LiteMap::new(); + /// map.insert(1, "one"); + /// map.insert(2, "two"); + /// assert_eq!(map.contains_key(&1), true); + /// assert_eq!(map.contains_key(&3), false); + /// ``` + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: Ord, + { + self.values + .binary_search_by(|k| k.0.borrow().cmp(&key)) + .is_ok() + } + + /// Get the value associated with `key`, if it exists, as a mutable reference. + /// + /// ```rust + /// use litemap::LiteMap; + /// + /// let mut map = LiteMap::new(); + /// map.insert(1, "one"); + /// map.insert(2, "two"); + /// if let Some(mut v) = map.get_mut(&1) { + /// *v = "uno"; + /// } + /// assert_eq!(map.get(&1), Some(&"uno")); + /// ``` + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Ord, + { + match self.values.binary_search_by(|k| k.0.borrow().cmp(&key)) { + Ok(found) => Some(&mut self.values[found].1), + Err(_) => None, + } + } + + /// Appends `value` with `key` to the end of the underlying vector, returning + /// `value` _if it failed_. Useful for extending with an existing sorted list. + /// + /// ```rust + /// use litemap::LiteMap; + /// + /// let mut map = LiteMap::new(); + /// assert!(map.try_append(1, "uno").is_none()); + /// assert!(map.try_append(3, "tres").is_none()); + /// // out of order append: + /// assert!(map.try_append(2, "dos").is_some()); + /// + /// assert_eq!(map.get(&1), Some(&"uno")); + /// // not appended since it wasn't in order + /// assert_eq!(map.get(&2), None); + /// ``` + pub fn try_append(&mut self, key: K, value: V) -> Option { + if let Some(ref last) = self.values.last() { + if last.0 > key { + return Some(value); + } + } + + self.values.push((key, value)); + None + } + + /// Insert `value` with `key`, returning the existing value if it exists. + /// + /// ```rust + /// use litemap::LiteMap; + /// + /// let mut map = LiteMap::new(); + /// map.insert(1, "one"); + /// map.insert(2, "two"); + /// assert_eq!(map.get(&1), Some(&"one")); + /// assert_eq!(map.get(&3), None); + /// ``` + pub fn insert(&mut self, key: K, value: V) -> Option { + match self.values.binary_search_by(|k| k.0.cmp(&key)) { + Ok(found) => Some(mem::replace(&mut self.values[found].1, value)), + Err(ins) => { + self.values.insert(ins, (key, value)); + None + } + } + } + + /// Remove the value at `key`, returning it if it exists. + /// + /// ```rust + /// use litemap::LiteMap; + /// + /// let mut map = LiteMap::new(); + /// map.insert(1, "one"); + /// map.insert(2, "two"); + /// assert_eq!(map.remove(&1), Some("one")); + /// assert_eq!(map.get(&1), None); + /// ``` + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Ord, + { + match self.values.binary_search_by(|k| k.0.borrow().cmp(key)) { + Ok(found) => Some(self.values.remove(found).1), + Err(_) => None, + } + } +} + +impl Default for LiteMap { + fn default() -> Self { + LiteMap { values: vec![] } + } +} +impl Index<&'_ K> for LiteMap { + type Output = V; + fn index(&self, key: &K) -> &V { + self.get(key).expect("LiteMap could not find key") + } +} +impl IndexMut<&'_ K> for LiteMap { + fn index_mut(&mut self, key: &K) -> &mut V { + self.get_mut(key).expect("LiteMap could not find key") + } +} + +impl LiteMap { + /// Produce an ordered iterator over key-value pairs + pub fn iter(&self) -> impl Iterator { + self.values.iter().map(|val| (&val.0, &val.1)) + } + + /// Produce an ordered iterator over keys + pub fn iter_keys(&self) -> impl Iterator { + self.values.iter().map(|val| &val.0) + } + + /// Produce an iterator over values, ordered by their keys + pub fn iter_values(&self) -> impl Iterator { + self.values.iter().map(|val| &val.1) + } + + /// Produce an ordered mutable iterator over key-value pairs + pub fn iter_mut(&mut self) -> impl Iterator { + self.values.iter_mut().map(|val| (&val.0, &mut val.1)) + } +}