Crate bimap

source ·
Expand description

A fast two-way bijective map.

A bimap is a bijective map between values of type L, called left values, and values of type R, called right values. This means every left value is associated with exactly one right value and vice versa. Compare this to a HashMap or BTreeMap, where every key is associated with exactly one value but a value can be associated with more than one key.

This crate provides two kinds of bimap: a BiHashMap and a BiBTreeMap. Internally, each one is composed of two maps, one for the left-to-right direction and one for right-to-left. As such, the big-O performance of the get, remove, insert, and contains methods are the same as those of the backing map.

For convenience, the type definition BiMap corresponds to a BiHashMap. If you’re using this crate without the standard library, it instead corresponds to a BiBTreeMap.

§Examples

use bimap::BiMap;

let mut elements = BiMap::new();

// insert chemicals and their corresponding symbols
elements.insert("hydrogen", "H");
elements.insert("carbon", "C");
elements.insert("bromine", "Br");
elements.insert("neodymium", "Nd");

// retrieve chemical symbol by name (left to right)
assert_eq!(elements.get_by_left(&"bromine"), Some(&"Br"));
assert_eq!(elements.get_by_left(&"oxygen"), None);

// retrieve name by chemical symbol (right to left)
assert_eq!(elements.get_by_right(&"C"), Some(&"carbon"));
assert_eq!(elements.get_by_right(&"Al"), None);

// check membership
assert!(elements.contains_left(&"hydrogen"));
assert!(!elements.contains_right(&"He"));

// remove elements
assert_eq!(
    elements.remove_by_left(&"neodymium"),
    Some(("neodymium", "Nd"))
);
assert_eq!(elements.remove_by_right(&"Nd"), None);

// iterate over elements
for (left, right) in &elements {
    println!("the chemical symbol for {} is {}", left, right);
}

§Insertion and overwriting

Consider the following example:

use bimap::BiMap;

let mut bimap = BiMap::new();
bimap.insert('a', 1);
bimap.insert('b', 1); // what to do here?

In order to maintain the bijection, the bimap cannot have both left-right pairs ('a', 1) and ('b', 1). Otherwise, the right-value 1 would have two left values associated with it. Either we should allow the call to insert to go through and overwrite ('a', 1), or not let ('b', 1) be inserted at all. This crate allows for both possibilities. To insert with overwriting, use insert, and to insert without overwriting, use insert_no_overwrite. The return type of insert is the enum Overwritten, which indicates what values, if any, were overwritten; the return type of insert_no_overwrite is a Result indicating if the insertion was successful.

This is especially important when dealing with types that can be equal while having different data. Unlike a HashMap or BTreeMap, which doesn’t update an equal key upon insertion, a bimap updates both the left values and the right values.

use bimap::{BiMap, Overwritten};
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};

#[derive(Clone, Copy, Debug)]
struct Foo {
    important: char,
    unimportant: u32,
}

// equality only depends on the important data
impl PartialEq for Foo {
    fn eq(&self, other: &Foo) -> bool {
        self.important == other.important
    }
}

impl Eq for Foo {}

impl PartialOrd for Foo {
    fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

// ordering only depends on the important data
impl Ord for Foo {
    fn cmp(&self, other: &Foo) -> Ordering {
        self.important.cmp(&other.important)
    }
}

// hash only depends on the important data
impl Hash for Foo {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.important.hash(state);
    }
}

// create two Foos that are equal but have different data
let foo1 = Foo {
    important: 'a',
    unimportant: 1,
};
let foo2 = Foo {
    important: 'a',
    unimportant: 2,
};
assert_eq!(foo1, foo2);

// insert both Foos into a bimap
let mut bimap = BiMap::new();
bimap.insert(foo1, 99);
let overwritten = bimap.insert(foo2, 100);

// foo1 is overwritten and returned
match overwritten {
    Overwritten::Left(foo, 99) => assert_eq!(foo.unimportant, foo1.unimportant),
    _ => unreachable!(),
};

// foo2 is in the bimap
assert_eq!(
    bimap.get_by_right(&100).unwrap().unimportant,
    foo2.unimportant
);

Note that the FromIterator and Extend implementations for both BiHashMap and BiBTreeMap use the insert method internally, meaning that values from the original iterator/collection can be silently overwritten.

use bimap::BiMap;
use std::iter::FromIterator;

// note that both 'b' and 'c' have the right-value 2
let mut bimap = BiMap::from_iter(vec![('a', 1), ('b', 2), ('c', 2)]);

// ('b', 2) was overwritten by ('c', 2)
assert_eq!(bimap.len(), 2);
assert_eq!(bimap.get_by_left(&'b'), None);
assert_eq!(bimap.get_by_left(&'c'), Some(&2));

§no_std compatibility

This crate can be used without the standard library when the std feature is disabled. If you choose to do this, only BiBTreeMap is available, not BiHashMap.

§serde compatibility

When the serde feature is enabled, implementations of Serialize and Deserialize are provided for BiHashMap and BiBTreeMap, allowing them to be serialized or deserialized painlessly. See the [serde] module for examples and more information.

Re-exports§

Modules§

  • A bimap backed by two BTreeMaps.
  • A bimap backed by two HashMaps.

Enums§

  • The previous left-right pairs, if any, that were overwritten by a call to the insert method of a bimap.

Type Aliases§

  • Type definition for convenience and compatibility with older versions of this crate.