From 2bb67017752ea163adcce2a33651b66e18ddb5d7 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 29 Feb 2024 13:42:01 -0600 Subject: [PATCH 1/8] Add HashMap docs --- .../standard_library/containers/hashmap.md | 253 ++++++++++++++++++ noir_stdlib/src/collections/map.nr | 111 ++++---- .../execution_success/hashmap/src/main.nr | 172 ++++++++++++ 3 files changed, 489 insertions(+), 47 deletions(-) create mode 100644 docs/docs/noir/standard_library/containers/hashmap.md diff --git a/docs/docs/noir/standard_library/containers/hashmap.md b/docs/docs/noir/standard_library/containers/hashmap.md new file mode 100644 index 00000000000..2ab7ac15175 --- /dev/null +++ b/docs/docs/noir/standard_library/containers/hashmap.md @@ -0,0 +1,253 @@ +--- +title: HashMap +keywords: [noir, map, hash, hashmap] +sidebar_position: 1 +--- + +SUMMARY + +Example: + +```rust +... +``` + +## Methods + +### default + +#include_code default noir_stdlib/src/collections/map.nr rust + +Creates a fresh, empty HashMap. + +When using this function, always make sure to specify the maximum size of the hash map. + +This is the same `default` from the `Default` implementation given further below. It is +repeated here for convenience since it is the recommended way to create a hashmap. + +Example: + +#include_code default_example test_programs/execution_success/hashmap/src/main.nr rust + +Because `HashMap` has so many generic arguments that are likely to be the same throughout +your program, it may be helpful to create a type alias: + +#include_code type_alias test_programs/execution_success/hashmap/src/main.nr rust + +### with_hasher + +#include_code with_hasher noir_stdlib/src/collections/map.nr rust + +Creates a hashmap with an existing `BuildHasher`. This can be used to ensure multiple +hashmaps are created with the same hasher instance. + +Example: + +#include_code with_hasher_example test_programs/execution_success/hashmap/src/main.nr rust + +### get + +#include_code get noir_stdlib/src/collections/map.nr rust + +Retrieves a value from the hashmap, returning `Option::none()` if it was not found. + +Example: + +#include_code get_example test_programs/execution_success/hashmap/src/main.nr rust + +### insert + +#include_code insert noir_stdlib/src/collections/map.nr rust + +Inserts a new key-value pair into the map. If the key was already in the map, its +previous value will be overrided with the newly provided one. + +Example: + +#include_code insert_example test_programs/execution_success/hashmap/src/main.nr rust + +### remove + +#include_code remove noir_stdlib/src/collections/map.nr rust + +Removes the given key-value pair from the map. If the key was not already present +in the map, this does nothing. + +Example: + +#include_code remove_example test_programs/execution_success/hashmap/src/main.nr rust + +### is_empty + +#include_code is_empty noir_stdlib/src/collections/map.nr rust + +True if the length of the hash map is empty. + +Example: + +#include_code is_empty_example test_programs/execution_success/hashmap/src/main.nr rust + +### len + +#include_code len noir_stdlib/src/collections/map.nr rust + +Returns the current length of this hash map. + +Example: + +#include_code len_example test_programs/execution_success/hashmap/src/main.nr rust + +### capacity + +#include_code capacity noir_stdlib/src/collections/map.nr rust + +Returns the maximum capacity of this hashmap. This is always equal to the capacity +specified in the hashmap's type. + +Unlike hashmaps in general purpose programming languages, hashmaps in Noir have a +static capacity that does not increase as the map grows larger. Thus, this capacity +is also the maximum possible element count that can be inserted into the hashmap. +Due to hash collisions (modulo the hashmap length), it is likely the actual maximum +element count will be lower than the full capacity. + +Example: + +#include_code capacity_example test_programs/execution_success/hashmap/src/main.nr rust + +### clear + +#include_code clear noir_stdlib/src/collections/map.nr rust + +Clears the hashmap, removing all key-value pairs from it. + +Example: + +#include_code clear_example test_programs/execution_success/hashmap/src/main.nr rust + +### contains_key + +#include_code contains_key noir_stdlib/src/collections/map.nr rust + +True if the hashmap contains the given key. Unlike `get`, this will not also return +the value associated with the key. + +Example: + +#include_code contains_key_example test_programs/execution_success/hashmap/src/main.nr rust + +### entries + +#include_code entries noir_stdlib/src/collections/map.nr rust + +Returns a vector of each key-value pair present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +#include_code entries_example test_programs/execution_success/hashmap/src/main.nr rust + +### keys + +#include_code keys noir_stdlib/src/collections/map.nr rust + +Returns a vector of each key present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +#include_code keys_example test_programs/execution_success/hashmap/src/main.nr rust + +### values + +#include_code values noir_stdlib/src/collections/map.nr rust + +Returns a vector of each value present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +#include_code values_example test_programs/execution_success/hashmap/src/main.nr rust + +### iter_mut + +#include_code iter_mut noir_stdlib/src/collections/map.nr rust + +Iterates through each key-value pair of the HashMap, setting each key-value pair to the +result returned from the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If this is not desired, use `iter_values_mut` if only values need to be mutated, +or `entries` if neither keys nor values need to be mutated. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +#include_code iter_mut_example test_programs/execution_success/hashmap/src/main.nr rust + +### iter_keys_mut + +#include_code iter_keys_mut noir_stdlib/src/collections/map.nr rust + +Iterates through the HashMap, mutating each key to the result returned from +the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If only iteration is desired and the keys are not intended to be mutated, +prefer using `entries` instead. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +#include_code iter_keys_mut_example test_programs/execution_success/hashmap/src/main.nr rust + +### iter_values_mut + +#include_code iter_values_mut noir_stdlib/src/collections/map.nr rust + +Iterates through the HashMap, applying the given function to each value and mutating the +value to equal the result. This function is more efficient than `iter_mut` and `iter_keys_mut` +because the keys are untouched and the underlying hashmap thus does not need to be reordered. + +Example: + +#include_code iter_values_mut_example test_programs/execution_success/hashmap/src/main.nr rust + +### retain + +#include_code retain noir_stdlib/src/collections/map.nr rust + +Retains only the key-value pairs for which the given function returns true. +Any key-value pairs for which the function returns false will be removed from the map. + +Example: + +#include_code retain_example test_programs/execution_success/hashmap/src/main.nr rust + +## Trait Implementations + +### default + +#include_code default noir_stdlib/src/collections/map.nr rust + +Constructs an empty HashMap. + +Example: + +#include_code default_example test_programs/execution_success/hashmap/src/main.nr rust + +### eq + +#include_code eq noir_stdlib/src/collections/map.nr rust + +Checks if two HashMaps are equal. + +Example: + +#include_code eq_example test_programs/execution_success/hashmap/src/main.nr rust diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index 056299b4238..17fa34c0e08 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -78,21 +78,26 @@ impl Slot { // it is very unlikely to be after - performance will be heavily degraded. impl HashMap { // Creates a new instance of HashMap with specified BuildHasher. + // docs:start:with_hasher pub fn with_hasher(_build_hasher: B) -> Self where B: BuildHasher { + // docs:end:with_hasher let _table = [Slot::default(); N]; let _len = 0; Self { _table, _len, _build_hasher } } // Clears the map, removing all key-value entries. + // docs:start:clear pub fn clear(&mut self) { + // docs:end:clear self._table = [Slot::default(); N]; self._len = 0; } // Returns true if the map contains a value for the specified key. + // docs:start:contains_key pub fn contains_key( self, key: K @@ -101,89 +106,80 @@ impl HashMap { K: Hash + Eq, B: BuildHasher, H: Hasher { + // docs:end:contains_key self.get(key).is_some() } // Returns true if the map contains no elements. + // docs:start:is_empty pub fn is_empty(self) -> bool { + // docs:end:is_empty self._len == 0 } - // Get the Option<(K, V) array of valid entries - // with a length of map capacity. First len() elements - // are safe to unwrap_unchecked(), whilst remaining - // are guaranteed to be Option::none(). - // - // This design is reasoned by compile-time limitations and - // temporary nested slices ban. - pub fn entries(self) -> [Option<(K, V)>; N] { - let mut entries = [Option::none(); N]; - let mut valid_amount = 0; + // Returns a BoundedVec of all valid entries in this HashMap. + // The length of the returned vector will always match the length of this HashMap. + // docs:start:entries + pub fn entries(self) -> BoundedVec<(K, V), N> { + // docs:end:keys + let mut entries = BoundedVec::new(); for slot in self._table { if slot.is_valid() { - entries[valid_amount] = slot.key_value(); - valid_amount += 1; + // SAFETY: slot.is_valid() should ensure there is a valid key-value pairing here + let key_value = slot.key_value().unwrap_unchecked(); + entries.push(key_value); } } - let msg = f"Amount of valid elements should have been {self._len} times, but got {valid_amount}."; - assert(valid_amount == self._len, msg); + let msg = f"Amount of valid elements should have been {self._len} times, but got {entries.len()}."; + assert(entries.len() == self._len, msg); entries } - // Get the Option array of valid keys - // with a length of map capacity. First len() elements - // are safe to unwrap_unchecked(), whilst remaining - // are guaranteed to be Option::none(). - // - // This design is reasoned by compile-time limitations and - // temporary nested slices ban. - pub fn keys(self) -> [Option; N] { - let mut keys = [Option::none(); N]; - let mut valid_amount = 0; + // Returns a BoundedVec containing all the keys within this HashMap. + // The length of the returned vector will always match the length of this HashMap. + // docs:start:keys + pub fn keys(self) -> BoundedVec { + // docs:end:keys + let mut keys = BoundedVec::new(); for slot in self._table { if slot.is_valid() { let (key, _) = slot.key_value_unchecked(); - keys[valid_amount] = Option::some(key); - valid_amount += 1; + keys.push(key); } } - let msg = f"Amount of valid elements should have been {self._len} times, but got {valid_amount}."; - assert(valid_amount == self._len, msg); + let msg = f"Amount of valid elements should have been {self._len} times, but got {keys.len()}."; + assert(keys.len() == self._len, msg); keys } - // Get the Option array of valid values - // with a length of map capacity. First len() elements - // are safe to unwrap_unchecked(), whilst remaining - // are guaranteed to be Option::none(). - // - // This design is reasoned by compile-time limitations and - // temporary nested slices ban. - pub fn values(self) -> [Option; N] { - let mut values = [Option::none(); N]; - let mut valid_amount = 0; + // Returns a BoundedVec containing all the values within this HashMap. + // The length of the returned vector will always match the length of this HashMap. + // docs:start:values + pub fn values(self) -> BoundedVec { + // docs:end:values + let mut values = BoundedVec::new(); for slot in self._table { if slot.is_valid() { let (_, value) = slot.key_value_unchecked(); - values[valid_amount] = Option::some(value); - valid_amount += 1; + values.push(value); } } - let msg = f"Amount of valid elements should have been {self._len} times, but got {valid_amount}."; - assert(valid_amount == self._len, msg); + let msg = f"Amount of valid elements should have been {self._len} times, but got {values.len()}."; + assert(values.len() == self._len, msg); values } // For each key-value entry applies mutator function. + // docs:start:iter_mut pub fn iter_mut( &mut self, f: fn(K, V) -> (K, V) @@ -192,12 +188,13 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:iter_mut let mut entries = self.entries(); let mut new_map = HashMap::with_hasher(self._build_hasher); for i in 0..N { if i < self._len { - let entry = entries[i].unwrap_unchecked(); + let entry = entries.get_unchecked(i); let (key, value) = f(entry.0, entry.1); new_map.insert(key, value); } @@ -207,6 +204,7 @@ impl HashMap { } // For each key applies mutator function. + // docs:start:iter_keys_mut pub fn iter_keys_mut( &mut self, f: fn(K) -> K @@ -215,12 +213,13 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:iter_keys_mut let mut entries = self.entries(); let mut new_map = HashMap::with_hasher(self._build_hasher); for i in 0..N { if i < self._len { - let entry = entries[i].unwrap_unchecked(); + let entry = entries.get_unchecked(i); let (key, value) = (f(entry.0), entry.1); new_map.insert(key, value); } @@ -230,7 +229,9 @@ impl HashMap { } // For each value applies mutator function. + // docs:start:iter_values_mut pub fn iter_values_mut(&mut self, f: fn(V) -> V) { + // docs:end:iter_values_mut for i in 0..N { let mut slot = self._table[i]; if slot.is_valid() { @@ -242,7 +243,9 @@ impl HashMap { } // Retains only the elements specified by the predicate. + // docs:start:retain pub fn retain(&mut self, f: fn(K, V) -> bool) { + // docs:end:retain for index in 0..N { let mut slot = self._table[index]; if slot.is_valid() { @@ -257,16 +260,21 @@ impl HashMap { } // Amount of active key-value entries. + // docs:start:len pub fn len(self) -> u64 { + // docs:end:len self._len } // Get the compile-time map capacity. + // docs:start:capacity pub fn capacity(_self: Self) -> u64 { + // docs:end:capacity N } // Get the value by key. If it does not exist, returns none(). + // docs:start:get pub fn get( self, key: K @@ -275,6 +283,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:get let mut result = Option::none(); let hash = self.hash(key); @@ -300,6 +309,7 @@ impl HashMap { } // Insert key-value entry. In case key was already present, value is overridden. + // docs:start:insert pub fn insert( &mut self, key: K, @@ -309,6 +319,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:insert self.assert_load_factor(); let hash = self.hash(key); @@ -340,7 +351,8 @@ impl HashMap { } } - // Remove key-value entry. If key is not present, HashMap remains unchanged. + // Removes a key-value entry. If key is not present, HashMap remains unchanged. + // docs:start:remove pub fn remove( &mut self, key: K @@ -349,6 +361,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:remove let hash = self.hash(key); let mut break = false; @@ -409,6 +422,7 @@ impl HashMap { // Equality class on HashMap has to test that they have // equal sets of key-value entries, // thus one is a subset of the other and vice versa. +// docs:start:eq impl Eq for HashMap where K: Eq + Hash, @@ -416,7 +430,8 @@ where B: BuildHasher, H: Hasher { - fn eq(self, other: HashMap) -> bool{ + fn eq(self, other: HashMap) -> bool { +// docs:end:eq let mut equal = false; if self.len() == other.len(){ @@ -443,12 +458,14 @@ where } } +// docs:start:eq impl Default for HashMap where B: BuildHasher + Default, H: Hasher + Default { - fn default() -> Self{ + fn default() -> Self { +// docs:end:eq let _build_hasher = B::default(); let map: HashMap = HashMap::with_hasher(_build_hasher); map diff --git a/test_programs/execution_success/hashmap/src/main.nr b/test_programs/execution_success/hashmap/src/main.nr index 597a5c0b7de..c99de6b2a3f 100644 --- a/test_programs/execution_success/hashmap/src/main.nr +++ b/test_programs/execution_success/hashmap/src/main.nr @@ -37,6 +37,8 @@ fn main(input: [Entry; HASHMAP_LEN]) { test_retain(); test_iterators(); test_mut_iterators(); + + doc_tests(); } // Insert, get, remove. @@ -190,3 +192,173 @@ fn test_mut_iterators() { assert(entries == [(12, 30), (30, 70), (66, 130)], "Got incorrect iteration of entries."); } + +// docs:start:type_alias +type MyMap = HashMap>; +// docs:end:type_alias + +/// Tests examples from the stdlib hashmap documentation +fn doc_tests() { + // docs:start:default_example + let hashmap: HashMap> = HashMap::default(); + assert(hashmap.is_empty()); + // docs:end:default_example + + // docs:start:with_hasher_example + let my_hasher: BuildHasherDefault = Default::default(); + let hashmap: HashMap> = HashMap::with_hasher(my_hasher); + assert(hashmap.is_empty()); + // docs:end:with_hasher_example + + // docs:start:insert_example + let mut map: HashMap> = HashMap::default(); + map.insert(12, 42); + assert(map.len() == 1); + // docs:end:insert_example + + get_example(map); + + // docs:start:remove_example + map.remove(12); + assert(map.is_empty()); + + // If a key was not present in the map, remove does nothing + map.remove(12); + assert(map.is_empty()); + // docs:end:remove_example + + // docs:start:is_empty_example + assert(map.is_empty()); + + map.insert(1, 2); + assert(!map.is_empty()); + + map.remove(1); + assert(map.is_empty()); + // docs:end:is_empty_example + + // docs:start:len_example + // This is equivalent to checking map.is_empty() + assert(map.len() == 0); + + map.insert(1, 2); + map.insert(3, 4); + map.insert(5, 6); + assert(map.len() == 3); + + // 3 was already present as a key in the hash map, so the length is unchanged + map.insert(3, 7); + assert(map.len() == 3); + + map.remove(1); + assert(map.len() == 2); + // docs:end:len_example + + // docs:start:capacity_example + let empty_map: HashMap> = HashMap::default(); + assert(empty_map.len() == 0); + assert(empty_map.capacity() == 42); + // docs:end:capacity_example + + // docs:start:clear_example + assert(!map.is_empty()); + map.clear(); + assert(map.is_empty()); + // docs:end:clear_example + + // docs:start:contains_key_example + if map.contains_key(7) { + let value = map.get(7); + assert(value.is_some()); + } else { + println("No value for key 7!"); + } + // docs:end:contains_key_example + + entries_examples(map); + iter_examples(map); + + // docs:start:retain_example + map.retain(|k, v| (k != 0) & (v != 0)); + // docs:end:retain_example + + // docs:start:eq_example + let mut map1: HashMap> = HashMap::default(); + let mut map2: HashMap> = HashMap::default(); + + map1.insert(1, 2); + map1.insert(3, 4); + + map2.insert(3, 4); + map2.insert(1, 2); + + assert(map1 == map2); + // docs:end:eq_example +} + +// docs:start:get_example +fn get_example(map: HashMap>) { + let x = map.get(12); + + if x.is_some() { + assert(x.unwrap() == 42); + } +} +// docs:end:get_example + +fn entries_examples(map: HashMap>) { + // docs:start:entries_example + let entries = map.entries(); + + // The length of a hashmap may not be compile-time known, so we + // need to loop over its capacity instead + for i in 0..map.capacity() { + let entry = entries.get(i); + + if entry.is_some() { + let (key, value) = entry.unwrap(); + println(f"{key} -> {value}"); + } + } + // docs:end:entries_example + + // docs:start:keys_example + let keys = map.keys(); + + for i in 0 .. keys.capacity() { + if i < keys.len() { + let key = keys.get_unchecked(i); + let value = map.get(key).unwrap_unchecked(); + println(f"{key} -> {value}"); + } + } + // docs:end:keys_example + + // docs:start:values_example + let values = map.values(); + + for i in 0 .. values.capacity() { + if i < values.len() { + let value = values.get_unchecked(i); + println(f"Found value {value}"); + } + } + // docs:end:values_example +} + +fn iter_examples(mut map: HashMap>) { + // docs:start:iter_mut_example + // Add 1 to each key in the map, and double the value associated with that key. + map.iter_mut(|k, v| (k + 1, v * 2)); + // docs:end:iter_mut_example + + // docs:start:iter_keys_mut_example + // Double each key, leaving the value associated with that key untouched + map.iter_keys_mut(|k| k * 2); + // docs:end:iter_keys_mut_example + + // docs:start:iter_values_mut_example + // Halve each value + map.iter_values_mut(|v| v / 2); + // docs:end:iter_values_mut_example +} From 12572e26cb6c2f4e8364ae9d343877a4e5eb2d08 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 29 Feb 2024 13:52:47 -0600 Subject: [PATCH 2/8] Fix HashMap test --- noir_stdlib/src/collections/map.nr | 1 + .../execution_success/hashmap/src/main.nr | 22 +++++++++---------- .../execution_success/hashmap/src/utils.nr | 10 ++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index 17fa34c0e08..67c0f080e81 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -3,6 +3,7 @@ use crate::collections::vec::Vec; use crate::option::Option; use crate::default::Default; use crate::hash::{Hash, Hasher, BuildHasher}; +use crate::collections::bounded_vec::BoundedVec; // We use load factor α_max = 0.75. // Upon exceeding it, assert will fail in order to inform the user diff --git a/test_programs/execution_success/hashmap/src/main.nr b/test_programs/execution_success/hashmap/src/main.nr index c99de6b2a3f..4d2cbd45993 100644 --- a/test_programs/execution_success/hashmap/src/main.nr +++ b/test_programs/execution_success/hashmap/src/main.nr @@ -156,9 +156,9 @@ fn test_iterators() { hashmap.insert(5, 7); hashmap.insert(11, 13); - let keys: [K; 3] = cut(hashmap.keys()).map(|k: Option| k.unwrap_unchecked()).sort_via(K_CMP); - let values: [V; 3] = cut(hashmap.values()).map(|v: Option| v.unwrap_unchecked()).sort_via(V_CMP); - let entries: [(K, V); 3] = cut(hashmap.entries()).map(|e: Option<(K, V)>| e.unwrap_unchecked()).sort_via(KV_CMP); + let keys: [K; 3] = cut(hashmap.keys()).sort_via(K_CMP); + let values: [V; 3] = cut(hashmap.values()).sort_via(V_CMP); + let entries: [(K, V); 3] = cut(hashmap.entries()).sort_via(KV_CMP); assert(keys == [2, 5, 11], "Got incorrect iteration of keys."); assert(values == [3, 7, 13], "Got incorrect iteration of values."); @@ -179,8 +179,8 @@ fn test_mut_iterators() { let f = |v: V| -> V{ v * 5}; hashmap.iter_values_mut(f); - let keys: [K; 3] = cut(hashmap.keys()).map(|k: Option| k.unwrap_unchecked()).sort_via(K_CMP); - let values: [V; 3] = cut(hashmap.values()).map(|v: Option| v.unwrap_unchecked()).sort_via(V_CMP); + let keys: [K; 3] = cut(hashmap.keys()).sort_via(K_CMP); + let values: [V; 3] = cut(hashmap.values()).sort_via(V_CMP); assert(keys == [6, 15, 33], f"Got incorrect iteration of keys: {keys}"); assert(values == [15, 35, 65], "Got incorrect iteration of values."); @@ -188,7 +188,7 @@ fn test_mut_iterators() { let f = |k: K, v: V| -> (K, V){(k * 2, v * 2)}; hashmap.iter_mut(f); - let entries: [(K, V); 3] = cut(hashmap.entries()).map(|e: Option<(K, V)>| e.unwrap_unchecked()).sort_via(KV_CMP); + let entries: [(K, V); 3] = cut(hashmap.entries()).sort_via(KV_CMP); assert(entries == [(12, 30), (30, 70), (66, 130)], "Got incorrect iteration of entries."); } @@ -313,10 +313,8 @@ fn entries_examples(map: HashMap {value}"); } } @@ -325,7 +323,7 @@ fn entries_examples(map: HashMap(input: [T; N]) -> [T; M] { +// Compile-time: cuts the M first elements from the BoundedVec. +pub(crate) fn cut(input: BoundedVec) -> [T; M] { assert(M as u64 < N as u64, "M should be less than N."); - let mut new = [dep::std::unsafe::zeroed(); M]; + let mut new = BoundedVec::new(); for i in 0..M { - new[i] = input[i]; + new.push(input.get(i)); } - new + new.storage() } From ea60e4a19f112283e8a91f579b70d7d633581038 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 29 Feb 2024 13:54:53 -0600 Subject: [PATCH 3/8] Overrided -> Overridden --- docs/docs/noir/standard_library/containers/hashmap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/noir/standard_library/containers/hashmap.md b/docs/docs/noir/standard_library/containers/hashmap.md index 2ab7ac15175..e4f19a0d838 100644 --- a/docs/docs/noir/standard_library/containers/hashmap.md +++ b/docs/docs/noir/standard_library/containers/hashmap.md @@ -60,7 +60,7 @@ Example: #include_code insert noir_stdlib/src/collections/map.nr rust Inserts a new key-value pair into the map. If the key was already in the map, its -previous value will be overrided with the newly provided one. +previous value will be overridden with the newly provided one. Example: From bc41f46c46fb67ae25ee4be92062faf38e78d5d1 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 29 Feb 2024 14:38:43 -0600 Subject: [PATCH 4/8] Format stdlib --- noir_stdlib/src/collections/map.nr | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index 67c0f080e81..f1d64e17ee8 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -83,7 +83,7 @@ impl HashMap { pub fn with_hasher(_build_hasher: B) -> Self where B: BuildHasher { - // docs:end:with_hasher + // docs:end:with_hasher let _table = [Slot::default(); N]; let _len = 0; Self { _table, _len, _build_hasher } @@ -92,7 +92,7 @@ impl HashMap { // Clears the map, removing all key-value entries. // docs:start:clear pub fn clear(&mut self) { - // docs:end:clear + // docs:end:clear self._table = [Slot::default(); N]; self._len = 0; } @@ -107,14 +107,14 @@ impl HashMap { K: Hash + Eq, B: BuildHasher, H: Hasher { - // docs:end:contains_key + // docs:end:contains_key self.get(key).is_some() } // Returns true if the map contains no elements. // docs:start:is_empty pub fn is_empty(self) -> bool { - // docs:end:is_empty + // docs:end:is_empty self._len == 0 } @@ -122,7 +122,7 @@ impl HashMap { // The length of the returned vector will always match the length of this HashMap. // docs:start:entries pub fn entries(self) -> BoundedVec<(K, V), N> { - // docs:end:keys + // docs:end:keys let mut entries = BoundedVec::new(); for slot in self._table { @@ -143,7 +143,7 @@ impl HashMap { // The length of the returned vector will always match the length of this HashMap. // docs:start:keys pub fn keys(self) -> BoundedVec { - // docs:end:keys + // docs:end:keys let mut keys = BoundedVec::new(); for slot in self._table { @@ -163,7 +163,7 @@ impl HashMap { // The length of the returned vector will always match the length of this HashMap. // docs:start:values pub fn values(self) -> BoundedVec { - // docs:end:values + // docs:end:values let mut values = BoundedVec::new(); for slot in self._table { @@ -189,7 +189,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { - // docs:end:iter_mut + // docs:end:iter_mut let mut entries = self.entries(); let mut new_map = HashMap::with_hasher(self._build_hasher); @@ -214,7 +214,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { - // docs:end:iter_keys_mut + // docs:end:iter_keys_mut let mut entries = self.entries(); let mut new_map = HashMap::with_hasher(self._build_hasher); @@ -232,7 +232,7 @@ impl HashMap { // For each value applies mutator function. // docs:start:iter_values_mut pub fn iter_values_mut(&mut self, f: fn(V) -> V) { - // docs:end:iter_values_mut + // docs:end:iter_values_mut for i in 0..N { let mut slot = self._table[i]; if slot.is_valid() { @@ -246,7 +246,7 @@ impl HashMap { // Retains only the elements specified by the predicate. // docs:start:retain pub fn retain(&mut self, f: fn(K, V) -> bool) { - // docs:end:retain + // docs:end:retain for index in 0..N { let mut slot = self._table[index]; if slot.is_valid() { @@ -263,14 +263,14 @@ impl HashMap { // Amount of active key-value entries. // docs:start:len pub fn len(self) -> u64 { - // docs:end:len + // docs:end:len self._len } // Get the compile-time map capacity. // docs:start:capacity pub fn capacity(_self: Self) -> u64 { - // docs:end:capacity + // docs:end:capacity N } @@ -284,7 +284,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { - // docs:end:get + // docs:end:get let mut result = Option::none(); let hash = self.hash(key); @@ -320,7 +320,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { - // docs:end:insert + // docs:end:insert self.assert_load_factor(); let hash = self.hash(key); @@ -362,7 +362,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { - // docs:end:remove + // docs:end:remove let hash = self.hash(key); let mut break = false; From 17427713b148c0d1889fb1b1b676a49ecf1e0963 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 29 Feb 2024 15:29:43 -0600 Subject: [PATCH 5/8] Add introduction --- .../standard_library/containers/hashmap.md | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/docs/noir/standard_library/containers/hashmap.md b/docs/docs/noir/standard_library/containers/hashmap.md index e4f19a0d838..093b6d38d11 100644 --- a/docs/docs/noir/standard_library/containers/hashmap.md +++ b/docs/docs/noir/standard_library/containers/hashmap.md @@ -4,12 +4,29 @@ keywords: [noir, map, hash, hashmap] sidebar_position: 1 --- -SUMMARY +`HashMap` is used to efficiently store and look up key-value pairs. + +`HashMap` is a bounded type which can store anywhere from zero to `MaxLen` total elements. +Note that due to hash collisions, the actual maximum number of elements stored by any particular +hashmap is likely lower than `MaxLen`. This is true even with cryptographic hash functions since +every hash value will be performed modulo `MaxLen`. + +When creating `HashMap`s, the `MaxLen` generic should always be specified if it is not already +known. Otherwise, the compiler may infer a different value for `MaxLen` (such as zero), which +will likely change the result of the program. This behavior is set to become an error in future +versions instead. Example: ```rust -... +// Create a mapping from Fields to u32s with a maximum length of 12 +// using a pedersen hash +let mut map: HashMap> = HashMap::default(); + +map.insert(1, 2); +map.insert(3, 4); + +let two = map.get(1).unwrap(); ``` ## Methods From 9a06609ff4bb5dea1c5d610a6fff8f1bbf3409f6 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 4 Mar 2024 09:03:28 -0600 Subject: [PATCH 6/8] Fix default typo --- noir_stdlib/src/collections/map.nr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index f1d64e17ee8..71df191aa67 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -459,14 +459,14 @@ where } } -// docs:start:eq +// docs:start:default impl Default for HashMap where B: BuildHasher + Default, H: Hasher + Default { fn default() -> Self { -// docs:end:eq +// docs:end:default let _build_hasher = B::default(); let map: HashMap = HashMap::with_hasher(_build_hasher); map From 956f49904cdb038f87a2c4ed1a60e530894d54a8 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 4 Mar 2024 09:25:36 -0600 Subject: [PATCH 7/8] Fix entries typo --- noir_stdlib/src/collections/map.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index 71df191aa67..2d76acf1f3a 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -122,7 +122,7 @@ impl HashMap { // The length of the returned vector will always match the length of this HashMap. // docs:start:entries pub fn entries(self) -> BoundedVec<(K, V), N> { - // docs:end:keys + // docs:end:entries let mut entries = BoundedVec::new(); for slot in self._table { From 73aa708a3afb5afa61e24356a0ebe0f1f2d844f3 Mon Sep 17 00:00:00 2001 From: jfecher Date: Mon, 4 Mar 2024 12:43:54 -0600 Subject: [PATCH 8/8] Update test_programs/execution_success/hashmap/src/utils.nr Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- test_programs/execution_success/hashmap/src/utils.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_programs/execution_success/hashmap/src/utils.nr b/test_programs/execution_success/hashmap/src/utils.nr index 0c70706f15e..ee73245a902 100644 --- a/test_programs/execution_success/hashmap/src/utils.nr +++ b/test_programs/execution_success/hashmap/src/utils.nr @@ -1,6 +1,6 @@ // Compile-time: cuts the M first elements from the BoundedVec. pub(crate) fn cut(input: BoundedVec) -> [T; M] { - assert(M as u64 < N as u64, "M should be less than N."); + assert(M < N, "M should be less than N."); let mut new = BoundedVec::new(); for i in 0..M {