std.map.Map
type pub Map[K: Equal + Hash, V]A hash map using linear probing and Robin Hood hashing.
A Map preserves the order in which values are inserted, even when entries
are removed.
Performance
Preserving the insertion order makes it easier to use a Map (e.g. when
writing tests or when serialising it), but comes with the trade-off that
removals are more expensive. Our implementation simply shifts values when
removing them. This makes removals more expensive compared to traditional maps
(O(n) in the worst case, with n being the number of entries), but removes
the need for using tombstones and extra indirection.
If you find yourself in a situation where you need to remove many entries from
a Map, it may be faster to construct a new Map that only contains the
key/value pairs you are interested in.
Size limitations
The maximum number of slots a Map can store is 2 147 483 648, which
corresponds to a maximum of 1 932 735 283 key-value pairs (due to the load
factor being 90%). When resizing a Map beyond this limit, a panic is
produced.
This limit shouldn't pose a problem for real-world scenarios, as most systems
won't have enough memory available to create such a Map in the first place.
In addition, a Map with so many values is likely the result of a bug or
better served by a more memory efficient data structure in the first place.
The reason for this limit is that Map uses signed 32-bits indexes internally
to conserve memory, with the limit being enforced during a resize.
Algorithm
Map uses Robin Hood hashing, with the necessary changes to make this work
while preserving insertion order. For more information on these algorithms you
can refer to the following resources:
- http://codecapsule.com/2013/11/11/robin-hood-hashing/
- http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
- https://www.sebastiansylvan.com/post/robin-hood-hashing-should-be-your-default-hash-table-implementation/
Static methods
new
Show source codeHide source code
fn pub static new -> Map[K, V] {
with_capacity(DEFAULT_CAPACITY)
}fn pub static new -> Map[K, V]Returns a new empty Map.
with_capacity
Show source codeHide source code
fn pub static with_capacity(amount: Int) -> Map[K, V] {
if amount <= 0 { invalid_capacity(amount) }
let mut size = amount.nearest_power_of_two
let mut resize_at = resize_threshold(size)
# If the amount is greater than the resize threshold we'd still need a
# resize, so in this case we double the capacity (due to it always being a
# power of two).
if resize_at < amount {
size = (size * 2).nearest_power_of_two
resize_at = resize_threshold(size)
}
if size > MAX_SIZE { map_too_large }
let slots = Array.filled(with: Slot.empty, times: size)
let entries = Array.with_capacity(amount)
Map(slots: slots, entries: entries, resize_at: resize_at)
}fn pub static with_capacity(amount: Int) -> Map[K, V]Returns a new Map with space for at least amount values before resizing
the Map is necessary.
The actual capacity may be greater due to the load factor of a Map, but it
will never be less than amount.
Panics
This method panics if amount is less than or equal to zero.
Examples
let map = Map.with_capacity(32)
map.capacity # => 57
map.set('name', 'Alice')
Instance methods
!=
Show source codeHide source code
fn pub !=(other: ref Self) -> Bool {
!(self == other)
}fn pub !=(other: ref Self) -> Bool
if
V: EqualReturns true if self and the given object are not equal to each other.
==
Show source codeHide source code
fn pub ==(other: ref Map[K, V]) -> Bool {
if size != other.size { return false }
for (k, v) in iter {
match other.entries_index(k) {
case EMPTY -> return false
case idx if other.entries.get(idx).or_panic.value != v -> return false
case _ -> {}
}
}
true
}fn pub ==(other: ref Map[K, V]) -> Bool
if
V: EqualReturns true if self and the given Map are identical to each
other.
Examples
Comparing two Map instances:
let map1 = Map.new
let map2 = Map.new
map1.set('name', 'Alice')
map2.set('name', 'Alice')
map1 == map2 # => true
capacity
Show source codeHide source code
fn pub capacity -> Int {
@resize_at
}fn pub capacity -> IntReturns the number of key-value pairs self can store before a resize is
required.
Examples
Map.new.capacity # => 7
Map.with_capacity(8).capacity # => 14
clear
Show source codeHide source code
fn pub mut clear {
@slots = Array.filled(with: Slot.empty, times: @slots.size)
@entries.clear
}fn pub mut clearRemoves all values in self.
Examples
let map = Map.new
map.set('name', 'Alice')
map.set('age', '42')
map.clear
map.size # => 0
clone
Show source codeHide source code
fn pub clone -> Map[K, V] {
Map(slots: @slots.clone, entries: @entries.clone, resize_at: @resize_at)
}fn pub clone -> Map[K, V]
if
K: Equal + Hash + Clone,
V: CloneCreates a clone of self.
The returned value is an owned value that is the same type as the receiver
of this method. For example, cloning a ref Array[Int] results in a
Array[Int], not another ref Array[Int].
contains?
Show source codeHide source code
fn pub contains?(value: ref K) -> Bool {
entries_index(value) > EMPTY
}fn pub contains?(value: ref K) -> BoolReturns true if self contains the key key.
Examples
let map = Map.new
map.set('name', 'Alice')
map.contains?('name') # => true
map.contains?('city') # => false
empty?
Show source codeHide source code
fn pub empty? -> Bool {
@entries.empty?
}fn pub empty? -> BoolReturns true if self is empty.
Examples
let map = Map.new
map.empty? # => true
map.set('name', 'Alice')
map.empty? # => false
fmt
Show source codeHide source code
fn pub fmt(formatter: mut Formatter) {
formatter.write('{')
for (index, (k, v)) in iter.with_index {
if index > 0 { formatter.write(', ') }
k.fmt(formatter)
formatter.write(': ')
v.fmt(formatter)
}
formatter.write('}')
}fn pub fmt(formatter: mut Formatter)
if
K: Equal + Hash + Format,
V: FormatFormats self in a human-readable format for debugging purposes.
get
Show source codeHide source code
fn pub get(key: ref K) -> Result[ref V, MissingKey[K]] {
match entries_index(key) {
case EMPTY -> Result.Error(MissingKey.new(key))
case index -> Result.Ok(@entries.get(index).or_panic.value)
}
}fn pub get(key: ref K) -> Result[ref V, MissingKey[K]]Returns an immutable borrow of the key's value.
If the key is missing, a MissingKey error is returned.
Examples
Getting the value of a missing key:
let map = Map.new
map.get('name') # => Result.Error(MissingKey(...))
Getting the value of an existing key:
let mut map = Map.new
map.set('name', 'Alice')
map.get('name') # => Result.Ok('Alice')
get_mut
Show source codeHide source code
fn pub mut get_mut(key: ref K) -> Result[mut V, MissingKey[K]] {
match entries_index(key) {
case EMPTY -> Result.Error(MissingKey.new(key))
case index -> Result.Ok(@entries.get_mut(index).or_panic.value)
}
}fn pub mut get_mut(key: ref K) -> Result[mut V: mut, MissingKey[K]]
if
V: mutReturns a mutable borrow of the key's value.
If the key is missing, a MissingKey error is returned.
Examples
Getting the value of a missing key:
let map = Map.new
map.get_mut('example') # => Result.Error(MissingKey(...))
Getting the value of an existing key:
let mut map = Map.new
map.set('example', (10, 20))
map.get_mut('example') # => Result.Ok(mut (10, 20))
hash
Show source codeHide source code
fn pub hash[H: mut + Hasher](hasher: mut H) {
for (k, v) in iter {
k.hash(hasher)
v.hash(hasher)
}
}fn pub hash[H: mut + Hasher](hasher: mut H: mut)
if
K: Equal + Hash + Hash,
V: HashWrites the hash for self into the given Hasher.
into_iter
Show source codeHide source code
fn pub move into_iter -> Stream[(K, V)] {
let iter = @entries.into_iter
Stream(fn move {
match iter.next {
case Some({ @key = k, @value = v }) -> Option.Some((k, v))
case _ -> Option.None
}
})
}fn pub move into_iter -> Stream[(K, V)]Returns an Iter that iterates over all key-value pairs in this
Map, yielding them by value.
Examples
let mut map = Map.new
map.set('name', 'Alice')
for entry in map.into_iter {
entry.key # => 'name'
entry.value # => 'Alice'
}
iter
Show source codeHide source code
fn pub iter -> Stream[(ref K, ref V)] {
let mut idx = 0
Stream(fn move {
match @entries.get(idx) {
case Ok(e) -> {
idx += 1
Option.Some((e.key, e.value))
}
case _ -> Option.None
}
})
}fn pub iter -> Stream[(ref K, ref V)]Returns an iterator of immutable key-value pairs.
Examples
Iterating over all the key-value pairs:
let mut map = Map.new
map.set('name', 'Alice')
for entry in map.iter {
entry.key # => 'name'
entry.value # => 'Alice'
}
iter_mut
Show source codeHide source code
fn pub mut iter_mut -> Stream[(ref K, mut V)] {
let mut idx = 0
Stream(fn move {
match @entries.get_mut(idx) {
case Ok(e) -> {
idx += 1
Option.Some((e.key, e.value))
}
case _ -> Option.None
}
})
}fn pub mut iter_mut -> Stream[(ref K, mut V: mut)]
if
V: mutReturns an iterator of mutable key-value pairs.
Examples
Iterating over all the key-value pairs:
let mut map = Map.new
map.set('name', 'Alice')
for entry in map.iter_mut {
entry.key # => 'name'
entry.value # => 'Alice'
}
keys
Show source codeHide source code
fn pub keys -> Stream[ref K] {
let mut idx = 0
Stream(fn move {
match @entries.get(idx) {
case Ok(e) -> {
idx += 1
Option.Some(e.key)
}
case _ -> Option.None
}
})
}fn pub keys -> Stream[ref K]Returns an Iter visiting all the keys in this Map.
Examples
Iterating over the keys in a Map:
let mut map = Map.new
map.set('name', 'Alice')
for key in map.keys {
key # => 'name'
}
merge
Show source codeHide source code
fn pub mut merge(other: Map[K, V]) {
for (k, v) in other { set(k, v) }
}fn pub mut merge(other: Map[K, V])Merges two Map objects together.
Examples
let map1 = Map.new
let map2 = Map.new
map1.set('name', 'Alice')
map2.set('city', 'Amsterdam')
map1.merge(map2)
map1['name'] # => 'Alice'
map2['city'] # => 'Amsterdam'
remove
Show source codeHide source code
fn pub mut remove(key: ref K) -> Result[V, MissingKey[K]] {
let mut slot_idx = index_for(hash_key(key))
let mut dist = 0
let mut slot = @slots.get(slot_idx).or_panic
loop {
if slot.empty? or dist > slot.distance { throw MissingKey.new(key) }
if @entries.get(slot.entry).or_panic.key == key { break }
slot_idx = index_for(slot_idx + 1)
slot = @slots.get(slot_idx).or_panic
dist += 1
}
let value = @entries.remove_at(slot.entry).or_panic.into_value
@slots.set(slot_idx, Slot.empty)
# Because we shifted the entries to the left, any slots pointing to entries
# _after_ the removed value have to be updated accordingly.
#
# We have to iterate over _all_ the slots because any slot can point to an
# entry after the one that was removed.
let mut shift_idx = 0
while shift_idx < @slots.size {
let shift = @slots.get(shift_idx).or_panic
if shift.entry > slot.entry { @slots.set(shift_idx, shift.reduce_entry) }
shift_idx += 1
}
let mut prev_slot = slot_idx
slot_idx = index_for(slot_idx + 1)
# Removing an entry means we migh be able to shift slots that were pushed to
# the right (= its distance is increased) due to a collision.
loop {
let slot = @slots.get(slot_idx).or_panic
if slot.empty? or slot.distance == 0 { break }
@slots.set(slot_idx, Slot.empty)
@slots.set(prev_slot, slot.reduce_distance)
prev_slot = slot_idx
slot_idx = index_for(slot_idx + 1)
}
Result.Ok(value)
}fn pub mut remove(key: ref K) -> Result[V, MissingKey[K]]Removes the given key, returning its value.
If the key is missing, a MissingKey error is returned.
Examples
Removing a non-existing key:
let mut map = Map.new
map.remove('name') # => Result.Error(MissingKey(...))
Removing an existing key:
let mut map = Map.new
map.set('name', 'Alice')
map.remove('name') # => Result.Ok('Alice')
set
Show source codeHide source code
fn pub mut set(key: K, value: V) -> Option[V] {
if size >= @resize_at { resize }
insert_entry(Entry(hash: hash_key(key), key: key, value: value))
}fn pub mut set(key: K, value: V) -> Option[V]Inserts the key and value into self, returning the previous value if there
is any.
Examples
Inserting a new key-value pair:
let mut map = Map.new
map.set('name', 'Alice') # => Option.Some('Alice')
size
Show source codeHide source code
fn pub size -> Int {
@entries.size
}fn pub size -> IntReturns the number of key-value pairs in this map.
Examples
Using an empty map:
let map = Map.new
map.size # => 0
Using a map with one key-value pair:
let map = Map.new
map.set('name', 'Alice')
map.size # => 1
try_set
Show source codeHide source code
fn pub mut try_set(key: K, value: V) -> Result[Nil, (K, mut V, V)] {
if size >= @resize_at { resize }
try_insert_entry(Entry(hash: hash_key(key), key: key, value: value))
}fn pub mut try_set(key: K, value: V: mut) -> Result[Nil, (K, mut V: mut, V: mut)]
if
V: mutTries to insert the new key and value into self.
If the key isn't already set, a Result.Ok(nil) is returned. If the key is
already set, a Result.Error is returned containing the provided key, a
mutable borrow of the existing value, and the new value.
Using this method it's possible to insert a new entry or update the value of an existing entry (provided it's a mutable value), without the need for hashing twice (= once to get the existing value, and once to insert a new value).
Examples
let map: Map[String, Array[Int]] = Map.new
map.try_set('numbers', [10]) # => Result.Ok(nil)
map.try_set('numbers', [50]) # => Result.Error(('numbers', mut [20], [50]))
match map.try_set('numbers', [50]) {
case Ok(_) -> {}
case Error((_, old, new)) -> old.append(new)
}
values
Show source codeHide source code
fn pub values -> Stream[ref V] {
let mut idx = 0
Stream(fn move {
match @entries.get(idx) {
case Ok(e) -> {
idx += 1
Option.Some(e.value)
}
case _ -> Option.None
}
})
}fn pub values -> Stream[ref V]Returns an iterator that yields immutable references to the values in
self.
Examples
Iterating over the values in a Map:
let mut map = Map.new
map.set('name', 'Alice')
for value in map.values {
value # => 'Alice'
}
values_mut
Show source codeHide source code
fn pub mut values_mut -> Stream[mut V] {
let mut idx = 0
Stream(fn move {
match @entries.get_mut(idx) {
case Ok(e) -> {
idx += 1
Option.Some(e.value)
}
case _ -> Option.None
}
})
}fn pub mut values_mut -> Stream[mut V: mut]
if
V: mutReturns an iterator that yields mutable references to the values in self.
Examples
Iterating over the values in a Map:
let mut map = Map.new
map.set('name', 'Alice')
for value in map.values_mut {
value # => 'Alice'
}
Implemented traits
Clone
impl Clone for Map
if
K: Equal + Hash + Clone,
V: CloneEqual
impl Equal for Map
if
V: EqualFormat
impl Format for Map
if
K: Equal + Hash + Format,
V: FormatHash
impl Hash for Map
if
K: Equal + Hash + Hash,
V: Hash