Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add litemap crate (TupleVecMap) #496

Merged
merged 9 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ members = [
"tools/benchmark/macros",
"tools/benchmark/memory",
"utils/fixed_decimal",
"utils/terrain",
"utils/writeable",
]

Expand Down
17 changes: 17 additions & 0 deletions utils/terrain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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 = "terrain"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small to moderate preference for using a functional name like "sorted_vec_map", only because "terrain" isn't really that good of a name for what we're building.

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."

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
zbraniecki marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 22 additions & 0 deletions utils/terrain/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 ).

//! ## `terrain`
//!
//! `terrain` is a crate providing [`VecMap`], 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 [`VecMap`] for less than 12 elements,
//! and upgrades itself gracefully for larger inputs.
//!


mod map;

pub use map::VecMap;
zbraniecki marked this conversation as resolved.
Show resolved Hide resolved
221 changes: 221 additions & 0 deletions utils/terrain/src/map.rs
Original file line number Diff line number Diff line change
@@ -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::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 VecMap<K, V> {
values: Vec<(K, V)>
zbraniecki marked this conversation as resolved.
Show resolved Hide resolved
}

impl<K, V> VecMap<K, V> {
/// Construct a new [`VecMap`]
pub fn new() -> Self {
Self::default()
}

/// The number of elements in the [`VecMap`]
pub fn len(&self) -> usize {
self.values.len()
}

sffc marked this conversation as resolved.
Show resolved Hide resolved
/// Remove all elements from the [`VecMap`]
pub fn clear(&mut self) {
self.values.clear()
zbraniecki marked this conversation as resolved.
Show resolved Hide resolved
}

/// Reserve capacity for `additional` more elements to be inserted into
/// the [`VecMap`] to avoid frequent reallocations.
///
/// See [`Vec::reserve()`] for more information.
pub fn reserve(&mut self, additional: usize) {
self.values.reserve(additional)
zbraniecki marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl<K: Ord, V> VecMap<K, V> {
/// Get the value associated with `key`, if it exists.
///
/// ```rust
/// use terrain::VecMap;
///
/// let mut map = VecMap::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: &K) -> Option<&V> {
sffc marked this conversation as resolved.
Show resolved Hide resolved
match self.values.binary_search_by(|k| k.0.cmp(&key)) {
zbraniecki marked this conversation as resolved.
Show resolved Hide resolved
Ok(found) => {
Some(&self.values[found].1)
}
Err(_) => {
None
}
}
}

/// Returns whether `key` is contained in this map
///
/// ```rust
/// use terrain::VecMap;
///
/// let mut map = VecMap::new();
/// map.insert(1, "one");
/// map.insert(2, "two");
/// assert_eq!(map.contains(&1), true);
/// assert_eq!(map.contains(&3), false);
/// ```
pub fn contains(&self, key: &K) -> bool {
Manishearth marked this conversation as resolved.
Show resolved Hide resolved
self.values.binary_search_by(|k| k.0.cmp(&key)).is_ok()
}

/// Get the value associated with `key`, if it exists, as a mutable reference.
///
/// ```rust
/// use terrain::VecMap;
///
/// let mut map = VecMap::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: &K) -> Option<&mut V> {
match self.values.binary_search_by(|k| k.0.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 terrain::VecMap;
///
/// let mut map = VecMap::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<V> {
if self.values.len() > 1 {
Manishearth marked this conversation as resolved.
Show resolved Hide resolved
if let Some(ref last) = self.values.get(self.values.len() - 1) {
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 terrain::VecMap;
///
/// let mut map = VecMap::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<V> {
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
}
}
}

sffc marked this conversation as resolved.
Show resolved Hide resolved
/// Remove the value at `key`, returning it if it exists.
///
/// ```rust
/// use terrain::VecMap;
///
/// let mut map = VecMap::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: &K) -> Option<V> {
match self.values.binary_search_by(|k| k.0.cmp(key)) {
Ok(found) => {
Some(self.values.remove(found).1)
}
Err(_) => {
None
}
}
}
sffc marked this conversation as resolved.
Show resolved Hide resolved
}

impl<K, V> Default for VecMap<K, V> {
fn default() -> Self {
VecMap {
values: vec![]
}
}
}
impl<K: Ord, V> Index<&'_ K> for VecMap<K, V> {
type Output = V;
fn index(&self, key: &K) -> &V {
self.get(key).expect("VecMap could not find key")
}
}
impl<K: Ord, V> IndexMut<&'_ K> for VecMap<K, V> {
fn index_mut(&mut self, key: &K) -> &mut V {
self.get_mut(key).expect("VecMap could not find key")
}
}


impl<K, V> VecMap<K, V> {
/// Produce an ordered iterator over key-value pairs
pub fn iter(&self) -> impl Iterator<Item=(&K, &V)> {
sffc marked this conversation as resolved.
Show resolved Hide resolved
self.values.iter().map(|val| (&val.0, &val.1))
}

/// Produce an ordered iterator over keys
pub fn iter_keys(&self) -> impl Iterator<Item=&K> {
self.values.iter().map(|val| &val.0)
}

/// Produce an iterator over values, ordered by their keys
pub fn iter_values(&self) -> impl Iterator<Item=&V> {
self.values.iter().map(|val| &val.1)
}

/// Produce an ordered mutable iterator over key-value pairs
pub fn iter_mut(&mut self) -> impl Iterator<Item=(&K, &mut V)> {
self.values.iter_mut().map(|val| (&val.0, &mut val.1))
}
}