diff options
author | Louis Pilfold <louis@lpil.uk> | 2023-11-21 14:39:00 +0000 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2023-11-21 15:11:37 +0000 |
commit | 6139295179325810d53613879613b4181188d56a (patch) | |
tree | 819b7bd4e26a50d5e45dc5d6e638078d8d56c4cf | |
parent | aec149b434c1ca537c2fe9d307113c226413e035 (diff) | |
download | gleam_stdlib-6139295179325810d53613879613b4181188d56a.tar.gz gleam_stdlib-6139295179325810d53613879613b4181188d56a.zip |
map -> dict
-rw-r--r-- | src/gleam/dict.gleam | 548 | ||||
-rw-r--r-- | src/gleam/dynamic.gleam | 34 | ||||
-rw-r--r-- | src/gleam/iterator.gleam | 16 | ||||
-rw-r--r-- | src/gleam/list.gleam | 18 | ||||
-rw-r--r-- | src/gleam/map.gleam | 532 | ||||
-rw-r--r-- | src/gleam/set.gleam | 28 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 2 | ||||
-rw-r--r-- | test/gleam/dict_test.gleam | 393 | ||||
-rw-r--r-- | test/gleam/dynamic_test.gleam | 70 | ||||
-rw-r--r-- | test/gleam/iterator_test.gleam | 4 | ||||
-rw-r--r-- | test/gleam/list_test.gleam | 16 | ||||
-rw-r--r-- | test/gleam/string_test.gleam | 6 |
12 files changed, 1095 insertions, 572 deletions
diff --git a/src/gleam/dict.gleam b/src/gleam/dict.gleam new file mode 100644 index 0000000..ca6d272 --- /dev/null +++ b/src/gleam/dict.gleam @@ -0,0 +1,548 @@ +import gleam/option.{type Option} + +/// A dictionary of keys and values. +/// +/// Any type can be used for the keys and values of a map, but all the keys +/// must be of the same type and all the values must be of the same type. +/// +/// Each key can only be present in a map once. +/// +/// Dicts are not ordered in any way, and any unintentional ordering is not to +/// be relied upon in your code as it may change in future versions of Erlang +/// or Gleam. +/// +/// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more +/// information. +/// +pub type Dict(key, value) + +/// Determines the number of key-value pairs in the dict. +/// This function runs in constant time and does not need to iterate the dict. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> size() +/// 0 +/// ``` +/// +/// ```gleam +/// > new() |> insert("key", "value") |> size() +/// 1 +/// ``` +/// +pub fn size(map: Dict(k, v)) -> Int { + do_size(map) +} + +@external(erlang, "maps", "size") +@external(javascript, "../gleam_stdlib.mjs", "map_size") +fn do_size(a: Dict(k, v)) -> Int + +/// Converts the map to a list of 2-element tuples `#(key, value)`, one for +/// each key-value pair in the dict. +/// +/// The tuples in the list have no specific order. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> to_list() +/// [] +/// ``` +/// +/// ```gleam +/// > new() |> insert("key", 0) |> to_list() +/// [#("key", 0)] +/// ``` +/// +pub fn to_list(map: Dict(key, value)) -> List(#(key, value)) { + do_to_list(map) +} + +@external(erlang, "maps", "to_list") +@external(javascript, "../gleam_stdlib.mjs", "map_to_list") +fn do_to_list(a: Dict(key, value)) -> List(#(key, value)) + +/// Converts a list of 2-element tuples `#(key, value)` to a dict. +/// +/// If two tuples have the same key the last one in the list will be the one +/// that is present in the dict. +/// +pub fn from_list(list: List(#(k, v))) -> Dict(k, v) { + do_from_list(list) +} + +@target(erlang) +@external(erlang, "maps", "from_list") +fn do_from_list(a: List(#(key, value))) -> Dict(key, value) + +@target(javascript) +fn fold_list_of_pair( + over list: List(#(k, v)), + from initial: Dict(k, v), +) -> Dict(k, v) { + case list { + [] -> initial + [x, ..rest] -> fold_list_of_pair(rest, insert(initial, x.0, x.1)) + } +} + +@target(javascript) +fn do_from_list(list: List(#(k, v))) -> Dict(k, v) { + fold_list_of_pair(list, new()) +} + +/// Determines whether or not a value present in the map for a given key. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> insert("a", 0) |> has_key("a") +/// True +/// ``` +/// +/// ```gleam +/// > new() |> insert("a", 0) |> has_key("b") +/// False +/// ``` +/// +pub fn has_key(map: Dict(k, v), key: k) -> Bool { + do_has_key(key, map) +} + +@target(erlang) +@external(erlang, "maps", "is_key") +fn do_has_key(a: key, b: Dict(key, v)) -> Bool + +@target(javascript) +fn do_has_key(key: k, map: Dict(k, v)) -> Bool { + get(map, key) != Error(Nil) +} + +/// Creates a fresh map that contains no values. +/// +pub fn new() -> Dict(key, value) { + do_new() +} + +@external(erlang, "maps", "new") +@external(javascript, "../gleam_stdlib.mjs", "new_map") +fn do_new() -> Dict(key, value) + +/// Fetches a value from a map for a given key. +/// +/// The map may not have a value for the key, so the value is wrapped in a +/// `Result`. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> insert("a", 0) |> get("a") +/// Ok(0) +/// ``` +/// +/// ```gleam +/// > new() |> insert("a", 0) |> get("b") +/// Error(Nil) +/// ``` +/// +pub fn get(from: Dict(key, value), get: key) -> Result(value, Nil) { + do_get(from, get) +} + +@external(erlang, "gleam_stdlib", "map_get") +@external(javascript, "../gleam_stdlib.mjs", "map_get") +fn do_get(a: Dict(key, value), b: key) -> Result(value, Nil) + +/// Inserts a value into the map with the given key. +/// +/// If the map already has a value for the given key then the value is +/// replaced with the new value. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> insert("a", 0) |> to_list +/// [#("a", 0)] +/// ``` +/// +/// ```gleam +/// > new() |> insert("a", 0) |> insert("a", 5) |> to_list +/// [#("a", 5)] +/// ``` +/// +pub fn insert(into map: Dict(k, v), for key: k, insert value: v) -> Dict(k, v) { + do_insert(key, value, map) +} + +@external(erlang, "maps", "put") +@external(javascript, "../gleam_stdlib.mjs", "map_insert") +fn do_insert(a: key, b: value, c: Dict(key, value)) -> Dict(key, value) + +/// Updates all values in a given map by calling a given function on each key +/// and value. +/// +/// ## Examples +/// +/// ```gleam +/// > [#(3, 3), #(2, 4)] +/// > |> from_list +/// > |> map_values(fn(key, value) { key * value }) +/// [#(3, 9), #(2, 8)] +/// ``` +/// +pub fn map_values(in map: Dict(k, v), with fun: fn(k, v) -> w) -> Dict(k, w) { + do_map_values(fun, map) +} + +@target(erlang) +@external(erlang, "maps", "map") +fn do_map_values(a: fn(key, value) -> b, b: Dict(key, value)) -> Dict(key, b) + +@target(javascript) +fn do_map_values(f: fn(key, value) -> b, map: Dict(key, value)) -> Dict(key, b) { + let f = fn(map, k, v) { insert(map, k, f(k, v)) } + map + |> fold(from: new(), with: f) +} + +/// Gets a list of all keys in a given dict. +/// +/// Dicts are not ordered so the keys are not returned in any specific order. Do +/// not write code that relies on the order keys are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// > keys([#("a", 0), #("b", 1)]) +/// ["a", "b"] +/// ``` +/// +pub fn keys(map: Dict(keys, v)) -> List(keys) { + do_keys(map) +} + +@target(erlang) +@external(erlang, "maps", "keys") +fn do_keys(a: Dict(keys, v)) -> List(keys) + +@target(javascript) +fn reverse_and_concat(remaining, accumulator) { + case remaining { + [] -> accumulator + [item, ..rest] -> reverse_and_concat(rest, [item, ..accumulator]) + } +} + +@target(javascript) +fn do_keys_acc(list: List(#(k, v)), acc: List(k)) -> List(k) { + case list { + [] -> reverse_and_concat(acc, []) + [x, ..xs] -> do_keys_acc(xs, [x.0, ..acc]) + } +} + +@target(javascript) +fn do_keys(map: Dict(k, v)) -> List(k) { + let list_of_pairs = + map + |> to_list + do_keys_acc(list_of_pairs, []) +} + +/// Gets a list of all values in a given dict. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order values are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// > values(from_list([#("a", 0), #("b", 1)])) +/// [0, 1] +/// ``` +/// +pub fn values(map: Dict(k, values)) -> List(values) { + do_values(map) +} + +@target(erlang) +@external(erlang, "maps", "values") +fn do_values(a: Dict(k, values)) -> List(values) + +@target(javascript) +fn do_values_acc(list: List(#(k, v)), acc: List(v)) -> List(v) { + case list { + [] -> reverse_and_concat(acc, []) + [x, ..xs] -> do_values_acc(xs, [x.1, ..acc]) + } +} + +@target(javascript) +fn do_values(map: Dict(k, v)) -> List(v) { + let list_of_pairs = + map + |> to_list + do_values_acc(list_of_pairs, []) +} + +/// Creates a new map from a given map, minus any entries that a given function +/// returns `False` for. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> filter(fn(key, value) { value != 0 }) +/// from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> filter(fn(key, value) { True }) +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn filter( + in map: Dict(k, v), + keeping predicate: fn(k, v) -> Bool, +) -> Dict(k, v) { + do_filter(predicate, map) +} + +@target(erlang) +@external(erlang, "maps", "filter") +fn do_filter(a: fn(key, value) -> Bool, b: Dict(key, value)) -> Dict(key, value) + +@target(javascript) +fn do_filter( + f: fn(key, value) -> Bool, + map: Dict(key, value), +) -> Dict(key, value) { + let insert = fn(map, k, v) { + case f(k, v) { + True -> insert(map, k, v) + _ -> map + } + } + map + |> fold(from: new(), with: insert) +} + +/// Creates a new map from a given map, only including any entries for which the +/// keys are in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> take(["b"]) +/// from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> take(["a", "b", "c"]) +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn take(from map: Dict(k, v), keeping desired_keys: List(k)) -> Dict(k, v) { + do_take(desired_keys, map) +} + +@target(erlang) +@external(erlang, "maps", "with") +fn do_take(a: List(k), b: Dict(k, v)) -> Dict(k, v) + +@target(javascript) +fn insert_taken( + map: Dict(k, v), + desired_keys: List(k), + acc: Dict(k, v), +) -> Dict(k, v) { + let insert = fn(taken, key) { + case get(map, key) { + Ok(value) -> insert(taken, key, value) + _ -> taken + } + } + case desired_keys { + [] -> acc + [x, ..xs] -> insert_taken(map, xs, insert(acc, x)) + } +} + +@target(javascript) +fn do_take(desired_keys: List(k), map: Dict(k, v)) -> Dict(k, v) { + insert_taken(map, desired_keys, new()) +} + +/// Creates a new map from a pair of given maps by combining their entries. +/// +/// If there are entries with the same keys in both maps the entry from the +/// second map takes precedence. +/// +/// ## Examples +/// +/// ```gleam +/// > let a = from_list([#("a", 0), #("b", 1)]) +/// > let b = from_list([#("b", 2), #("c", 3)]) +/// > merge(a, b) +/// from_list([#("a", 0), #("b", 2), #("c", 3)]) +/// ``` +/// +pub fn merge(into map: Dict(k, v), from new_entries: Dict(k, v)) -> Dict(k, v) { + do_merge(map, new_entries) +} + +@target(erlang) +@external(erlang, "maps", "merge") +fn do_merge(a: Dict(k, v), b: Dict(k, v)) -> Dict(k, v) + +@target(javascript) +fn insert_pair(map: Dict(k, v), pair: #(k, v)) -> Dict(k, v) { + insert(map, pair.0, pair.1) +} + +@target(javascript) +fn fold_inserts(new_entries: List(#(k, v)), map: Dict(k, v)) -> Dict(k, v) { + case new_entries { + [] -> map + [x, ..xs] -> fold_inserts(xs, insert_pair(map, x)) + } +} + +@target(javascript) +fn do_merge(map: Dict(k, v), new_entries: Dict(k, v)) -> Dict(k, v) { + new_entries + |> to_list + |> fold_inserts(map) +} + +/// Creates a new map from a given map with all the same entries except for the +/// one with a given key, if it exists. +/// +/// ## Examples +/// +/// ```gleam +/// > delete([#("a", 0), #("b", 1)], "a") +/// from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// > delete([#("a", 0), #("b", 1)], "c") +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn delete(from map: Dict(k, v), delete key: k) -> Dict(k, v) { + do_delete(key, map) +} + +@external(erlang, "maps", "remove") +@external(javascript, "../gleam_stdlib.mjs", "map_remove") +fn do_delete(a: k, b: Dict(k, v)) -> Dict(k, v) + +/// Creates a new map from a given map with all the same entries except any with +/// keys found in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// > drop([#("a", 0), #("b", 1)], ["a"]) +/// from_list([#("b", 2)]) +/// ``` +/// +/// ```gleam +/// > delete([#("a", 0), #("b", 1)], ["c"]) +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +/// ```gleam +/// > drop([#("a", 0), #("b", 1)], ["a", "b", "c"]) +/// from_list([]) +/// ``` +/// +pub fn drop(from map: Dict(k, v), drop disallowed_keys: List(k)) -> Dict(k, v) { + case disallowed_keys { + [] -> map + [x, ..xs] -> drop(delete(map, x), xs) + } +} + +/// Creates a new map with one entry updated using a given function. +/// +/// If there was not an entry in the map for the given key then the function +/// gets `None` as its argument, otherwise it gets `Some(value)`. +/// +/// ## Example +/// +/// ```gleam +/// > let increment = fn(x) { +/// > case x { +/// > Some(i) -> i + 1 +/// > None -> 0 +/// > } +/// > } +/// > let map = from_list([#("a", 0)]) +/// > +/// > update(map, "a", increment) +/// from_list([#("a", 1)]) +/// ``` +/// +/// ```gleam +/// > update(map, "b", increment) +/// from_list([#("a", 0), #("b", 0)]) +/// ``` +/// +pub fn update( + in map: Dict(k, v), + update key: k, + with fun: fn(Option(v)) -> v, +) -> Dict(k, v) { + map + |> get(key) + |> option.from_result + |> fun + |> insert(map, key, _) +} + +fn do_fold(list: List(#(k, v)), initial: acc, fun: fn(acc, k, v) -> acc) -> acc { + case list { + [] -> initial + [#(k, v), ..rest] -> do_fold(rest, fun(initial, k, v), fun) + } +} + +/// Combines all entries into a single value by calling a given function on each +/// one. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order entries are used by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// # Examples +/// +/// ```gleam +/// > let map = from_list([#("a", 1), #("b", 3), #("c", 9)]) +/// > fold(map, 0, fn(accumulator, key, value) { accumulator + value }) +/// 13 +/// ``` +/// +/// ```gleam +/// > import gleam/string.{append} +/// > fold(map, "", fn(accumulator, key, value) { append(accumulator, key) }) +/// "abc" +/// ``` +/// +pub fn fold( + over map: Dict(k, v), + from initial: acc, + with fun: fn(acc, k, v) -> acc, +) -> acc { + map + |> to_list + |> do_fold(initial, fun) +} diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index de8cc9e..c8bf069 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -1,6 +1,6 @@ import gleam/int import gleam/list -import gleam/map.{type Map} +import gleam/dict.{type Dict} import gleam/option.{type Option} import gleam/result import gleam/string_builder @@ -400,9 +400,9 @@ fn decode_optional(a: Dynamic, b: Decoder(a)) -> Result(Option(a), DecodeErrors) /// ## Examples /// /// ```gleam -/// > import gleam/map -/// > map.new() -/// > |> map.insert("Hello", "World") +/// > import gleam/dict +/// > dict.new() +/// > |> dict.insert("Hello", "World") /// > |> from /// > |> field(named: "Hello", of: string) /// Ok("World") @@ -434,17 +434,17 @@ pub fn field(named name: a, of inner_type: Decoder(t)) -> Decoder(t) { /// ## Examples /// /// ```gleam -/// > import gleam/map -/// > map.new() -/// > |> map.insert("Hello", "World") +/// > import gleam/dict +/// > dict.new() +/// > |> dict.insert("Hello", "World") /// > |> from /// > |> field(named: "Hello", of: string) /// Ok(Some("World")) /// ``` /// /// ```gleam -/// > import gleam/map -/// > map.new() +/// > import gleam/dict +/// > dict.new() /// > |> from /// > |> field(named: "Hello", of: string) /// Ok(None) @@ -931,14 +931,14 @@ pub fn tuple6( } } -/// Checks to see if a `Dynamic` value is a map. +/// Checks to see if a `Dynamic` value is a dict. /// /// ## Examples /// /// ```gleam -/// > import gleam/map -/// > map.new() |> from |> map(string, int) -/// Ok(map.new()) +/// > import gleam/dict +/// > dict.new() |> from |> map(string, int) +/// Ok(dict.new()) /// ``` /// /// ```gleam @@ -954,12 +954,12 @@ pub fn tuple6( pub fn map( of key_type: Decoder(k), to value_type: Decoder(v), -) -> Decoder(Map(k, v)) { +) -> Decoder(Dict(k, v)) { fn(value) { use map <- result.try(decode_map(value)) use pairs <- result.try( map - |> map.to_list + |> dict.to_list |> list.try_map(fn(pair) { let #(k, v) = pair use k <- result.try( @@ -973,13 +973,13 @@ pub fn map( Ok(#(k, v)) }), ) - Ok(map.from_list(pairs)) + Ok(dict.from_list(pairs)) } } @external(erlang, "gleam_stdlib", "decode_map") @external(javascript, "../gleam_stdlib.mjs", "decode_map") -fn decode_map(a: Dynamic) -> Result(Map(Dynamic, Dynamic), DecodeErrors) +fn decode_map(a: Dynamic) -> Result(Dict(Dynamic, Dynamic), DecodeErrors) /// Joins multiple decoders into one. When run they will each be tried in turn /// until one succeeds, or they all fail. diff --git a/src/gleam/iterator.gleam b/src/gleam/iterator.gleam index 4e9d82a..c57e7fd 100644 --- a/src/gleam/iterator.gleam +++ b/src/gleam/iterator.gleam @@ -1,7 +1,7 @@ import gleam/result import gleam/int import gleam/list -import gleam/map.{type Map} +import gleam/dict.{type Dict} import gleam/option.{type Option, None, Some} import gleam/order @@ -1161,14 +1161,14 @@ fn update_group_with(el: element) -> fn(Option(List(element))) -> List(element) fn group_updater( f: fn(element) -> key, -) -> fn(Map(key, List(element)), element) -> Map(key, List(element)) { +) -> fn(Dict(key, List(element)), element) -> Dict(key, List(element)) { fn(groups, elem) { groups - |> map.update(f(elem), update_group_with(elem)) + |> dict.update(f(elem), update_group_with(elem)) } } -/// Returns a `Map(k, List(element))` of elements from the given iterator +/// Returns a `Dict(k, List(element))` of elements from the given iterator /// grouped with the given key function. /// /// The order within each group is preserved from the iterator. @@ -1177,16 +1177,16 @@ fn group_updater( /// /// ```gleam /// > from_list([1, 2, 3, 4, 5, 6]) |> group(by: fn(n) { n % 3 }) -/// map.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])]) +/// dict.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])]) /// ``` /// pub fn group( in iterator: Iterator(element), by key: fn(element) -> key, -) -> Map(key, List(element)) { +) -> Dict(key, List(element)) { iterator - |> fold(map.new(), group_updater(key)) - |> map.map_values(fn(_, group) { list.reverse(group) }) + |> fold(dict.new(), group_updater(key)) + |> dict.map_values(fn(_, group) { list.reverse(group) }) } /// This function acts similar to fold, but does not take an initial state. diff --git a/src/gleam/list.gleam b/src/gleam/list.gleam index 0b4c627..af206c9 100644 --- a/src/gleam/list.gleam +++ b/src/gleam/list.gleam @@ -26,7 +26,7 @@ import gleam/int import gleam/float import gleam/order.{type Order} import gleam/pair -import gleam/map.{type Map} +import gleam/dict.{type Dict} /// An error value returned by the `strict_zip` function. /// @@ -249,11 +249,11 @@ pub fn rest(list: List(a)) -> Result(List(a), Nil) { fn update_group( f: fn(element) -> key, -) -> fn(Map(key, List(element)), element) -> Map(key, List(element)) { +) -> fn(Dict(key, List(element)), element) -> Dict(key, List(element)) { fn(groups, elem) { - case map.get(groups, f(elem)) { - Ok(existing) -> map.insert(groups, f(elem), [elem, ..existing]) - Error(_) -> map.insert(groups, f(elem), [elem]) + case dict.get(groups, f(elem)) { + Ok(existing) -> dict.insert(groups, f(elem), [elem, ..existing]) + Error(_) -> dict.insert(groups, f(elem), [elem]) } } } @@ -273,7 +273,7 @@ fn update_group( /// Error(_) -> "Failed" /// } /// }) -/// |> map.to_list +/// |> dict.to_list /// /// [ /// #("Failed", [Error("Wrong")]), @@ -281,12 +281,12 @@ fn update_group( /// ] /// /// > group([1,2,3,4,5], by: fn(i) { i - i / 3 * 3 }) -/// |> map.to_list +/// |> dict.to_list /// [#(0, [3]), #(1, [4, 1]), #(2, [5, 2])] /// ``` /// -pub fn group(list: List(v), by key: fn(v) -> k) -> Map(k, List(v)) { - fold(list, map.new(), update_group(key)) +pub fn group(list: List(v), by key: fn(v) -> k) -> Dict(k, List(v)) { + fold(list, dict.new(), update_group(key)) } fn do_filter(list: List(a), fun: fn(a) -> Bool, acc: List(a)) -> List(a) { diff --git a/src/gleam/map.gleam b/src/gleam/map.gleam index 21ac4f8..1f8b228 100644 --- a/src/gleam/map.gleam +++ b/src/gleam/map.gleam @@ -1,233 +1,55 @@ import gleam/option.{type Option} +import gleam/dict -/// A dictionary of keys and values. -/// -/// Any type can be used for the keys and values of a map, but all the keys -/// must be of the same type and all the values must be of the same type. -/// -/// Each key can only be present in a map once. -/// -/// Maps are not ordered in any way, and any unintentional ordering is not to -/// be relied upon in your code as it may change in future versions of Erlang -/// or Gleam. -/// -/// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more -/// information. -/// -pub type Map(key, value) +@deprecated("Please use the `gleam/dict` module instead") +pub type Map(key, value) = + dict.Dict(key, value) -/// Determines the number of key-value pairs in the map. -/// This function runs in constant time and does not need to iterate the map. -/// -/// ## Examples -/// -/// ```gleam -/// > new() |> size() -/// 0 -/// ``` -/// -/// ```gleam -/// > new() |> insert("key", "value") |> size() -/// 1 -/// ``` -/// -pub fn size(map: Map(k, v)) -> Int { - do_size(map) +@deprecated("Please use the `gleam/dict` module instead") +pub fn size(map) -> Int { + dict.size(map) } -@external(erlang, "maps", "size") -@external(javascript, "../gleam_stdlib.mjs", "map_size") -fn do_size(a: Map(k, v)) -> Int - -/// Converts the map to a list of 2-element tuples `#(key, value)`, one for -/// each key-value pair in the map. -/// -/// The tuples in the list have no specific order. -/// -/// ## Examples -/// -/// ```gleam -/// > new() |> to_list() -/// [] -/// ``` -/// -/// ```gleam -/// > new() |> insert("key", 0) |> to_list() -/// [#("key", 0)] -/// ``` -/// -pub fn to_list(map: Map(key, value)) -> List(#(key, value)) { - do_to_list(map) +@deprecated("Please use the `gleam/dict` module instead") +pub fn to_list(map) -> List(#(key, value)) { + dict.to_list(map) } -@external(erlang, "maps", "to_list") -@external(javascript, "../gleam_stdlib.mjs", "map_to_list") -fn do_to_list(a: Map(key, value)) -> List(#(key, value)) - -/// Converts a list of 2-element tuples `#(key, value)` to a map. -/// -/// If two tuples have the same key the last one in the list will be the one -/// that is present in the map. -/// -pub fn from_list(list: List(#(k, v))) -> Map(k, v) { - do_from_list(list) -} - -@target(erlang) -@external(erlang, "maps", "from_list") -fn do_from_list(a: List(#(key, value))) -> Map(key, value) - -@target(javascript) -fn fold_list_of_pair( - over list: List(#(k, v)), - from initial: Map(k, v), -) -> Map(k, v) { - case list { - [] -> initial - [x, ..rest] -> fold_list_of_pair(rest, insert(initial, x.0, x.1)) - } -} - -@target(javascript) -fn do_from_list(list: List(#(k, v))) -> Map(k, v) { - fold_list_of_pair(list, new()) -} - -/// Determines whether or not a value present in the map for a given key. -/// -/// ## Examples -/// -/// ```gleam -/// > new() |> insert("a", 0) |> has_key("a") -/// True -/// ``` -/// -/// ```gleam -/// > new() |> insert("a", 0) |> has_key("b") -/// False -/// ``` -/// -pub fn has_key(map: Map(k, v), key: k) -> Bool { - do_has_key(key, map) -} - -@target(erlang) -@external(erlang, "maps", "is_key") -fn do_has_key(a: key, b: Map(key, v)) -> Bool - -@target(javascript) -fn do_has_key(key: k, map: Map(k, v)) -> Bool { - get(map, key) != Error(Nil) +@deprecated("Please use the `gleam/dict` module instead") +pub fn from_list(list: List(#(k, v))) { + dict.from_list(list) } -/// Creates a fresh map that contains no values. -/// -pub fn new() -> Map(key, value) { - do_new() +@deprecated("Please use the `gleam/dict` module instead") +pub fn has_key(map, key: k) -> Bool { + dict.has_key(map, key) } -@external(erlang, "maps", "new") -@external(javascript, "../gleam_stdlib.mjs", "new_map") -fn do_new() -> Map(key, value) - -/// Fetches a value from a map for a given key. -/// -/// The map may not have a value for the key, so the value is wrapped in a -/// `Result`. -/// -/// ## Examples -/// -/// ```gleam -/// > new() |> insert("a", 0) |> get("a") -/// Ok(0) -/// ``` -/// -/// ```gleam -/// > new() |> insert("a", 0) |> get("b") -/// Error(Nil) -/// ``` -/// -pub fn get(from: Map(key, value), get: key) -> Result(value, Nil) { - do_get(from, get) +@deprecated("Please use the `gleam/dict` module instead") +pub fn new() { + dict.new() } -@external(erlang, "gleam_stdlib", "map_get") -@external(javascript, "../gleam_stdlib.mjs", "map_get") -fn do_get(a: Map(key, value), b: key) -> Result(value, Nil) - -/// Inserts a value into the map with the given key. -/// -/// If the map already has a value for the given key then the value is -/// replaced with the new value. -/// -/// ## Examples -/// -/// ```gleam -/// > new() |> insert("a", 0) |> to_list -/// [#("a", 0)] -/// ``` -/// -/// ```gleam -/// > new() |> insert("a", 0) |> insert("a", 5) |> to_list -/// [#("a", 5)] -/// ``` -/// -pub fn insert(into map: Map(k, v), for key: k, insert value: v) -> Map(k, v) { - do_insert(key, value, map) +@deprecated("Please use the `gleam/dict` module instead") +pub fn get(from, get: key) -> Result(value, Nil) { + dict.get(from, get) } -@external(erlang, "maps", "put") -@external(javascript, "../gleam_stdlib.mjs", "map_insert") -fn do_insert(a: key, b: value, c: Map(key, value)) -> Map(key, value) - -/// Updates all values in a given map by calling a given function on each key -/// and value. -/// -/// ## Examples -/// -/// ```gleam -/// > [#(3, 3), #(2, 4)] -/// > |> from_list -/// > |> map_values(fn(key, value) { key * value }) -/// [#(3, 9), #(2, 8)] -/// ``` -/// -pub fn map_values(in map: Map(k, v), with fun: fn(k, v) -> w) -> Map(k, w) { - do_map_values(fun, map) +@deprecated("Please use the `gleam/dict` module instead") +pub fn insert(into map, for key: k, insert value: v) { + dict.insert(map, key, value) } -@target(erlang) -@external(erlang, "maps", "map") -fn do_map_values(a: fn(key, value) -> b, b: Map(key, value)) -> Map(key, b) - -@target(javascript) -fn do_map_values(f: fn(key, value) -> b, map: Map(key, value)) -> Map(key, b) { - let f = fn(map, k, v) { insert(map, k, f(k, v)) } - map - |> fold(from: new(), with: f) +@deprecated("Please use the `gleam/dict` module instead") +pub fn map_values(in map, with fun: fn(k, v) -> w) { + dict.map_values(map, fun) } -/// Gets a list of all keys in a given map. -/// -/// Maps are not ordered so the keys are not returned in any specific order. Do -/// not write code that relies on the order keys are returned by this function -/// as it may change in later versions of Gleam or Erlang. -/// -/// ## Examples -/// -/// ```gleam -/// > keys([#("a", 0), #("b", 1)]) -/// ["a", "b"] -/// ``` -/// -pub fn keys(map: Map(keys, v)) -> List(keys) { - do_keys(map) +@deprecated("Please use the `gleam/dict` module instead") +pub fn keys(map) -> List(keys) { + dict.keys(map) } -@target(erlang) -@external(erlang, "maps", "keys") -fn do_keys(a: Map(keys, v)) -> List(keys) - @target(javascript) fn reverse_and_concat(remaining, accumulator) { case remaining { @@ -245,80 +67,25 @@ fn do_keys_acc(list: List(#(k, v)), acc: List(k)) -> List(k) { } @target(javascript) -fn do_keys(map: Map(k, v)) -> List(k) { +fn do_keys(map) -> List(k) { let list_of_pairs = map |> to_list do_keys_acc(list_of_pairs, []) } -/// Gets a list of all values in a given map. -/// -/// Maps are not ordered so the values are not returned in any specific order. Do -/// not write code that relies on the order values are returned by this function -/// as it may change in later versions of Gleam or Erlang. -/// -/// ## Examples -/// -/// ```gleam -/// > values(from_list([#("a", 0), #("b", 1)])) -/// [0, 1] -/// ``` -/// -pub fn values(map: Map(k, values)) -> List(values) { - do_values(map) +@deprecated("Please use the `gleam/dict` module instead") +pub fn values(map) -> List(values) { + dict.values(map) } -@target(erlang) -@external(erlang, "maps", "values") -fn do_values(a: Map(k, values)) -> List(values) - -@target(javascript) -fn do_values_acc(list: List(#(k, v)), acc: List(v)) -> List(v) { - case list { - [] -> reverse_and_concat(acc, []) - [x, ..xs] -> do_values_acc(xs, [x.1, ..acc]) - } +@deprecated("Please use the `gleam/dict` module instead") +pub fn filter(in map, keeping predicate: fn(k, v) -> Bool) { + dict.filter(map, predicate) } @target(javascript) -fn do_values(map: Map(k, v)) -> List(v) { - let list_of_pairs = - map - |> to_list - do_values_acc(list_of_pairs, []) -} - -/// Creates a new map from a given map, minus any entries that a given function -/// returns `False` for. -/// -/// ## Examples -/// -/// ```gleam -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> filter(fn(key, value) { value != 0 }) -/// from_list([#("b", 1)]) -/// ``` -/// -/// ```gleam -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> filter(fn(key, value) { True }) -/// from_list([#("a", 0), #("b", 1)]) -/// ``` -/// -pub fn filter( - in map: Map(k, v), - keeping predicate: fn(k, v) -> Bool, -) -> Map(k, v) { - do_filter(predicate, map) -} - -@target(erlang) -@external(erlang, "maps", "filter") -fn do_filter(a: fn(key, value) -> Bool, b: Map(key, value)) -> Map(key, value) - -@target(javascript) -fn do_filter(f: fn(key, value) -> Bool, map: Map(key, value)) -> Map(key, value) { +fn do_filter(f: fn(key, value) -> Bool, map) { let insert = fn(map, k, v) { case f(k, v) { True -> insert(map, k, v) @@ -329,217 +96,32 @@ fn do_filter(f: fn(key, value) -> Bool, map: Map(key, value)) -> Map(key, value) |> fold(from: new(), with: insert) } -/// Creates a new map from a given map, only including any entries for which the -/// keys are in a given list. -/// -/// ## Examples -/// -/// ```gleam -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> take(["b"]) -/// from_list([#("b", 1)]) -/// ``` -/// -/// ```gleam -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> take(["a", "b", "c"]) -/// from_list([#("a", 0), #("b", 1)]) -/// ``` -/// -pub fn take(from map: Map(k, v), keeping desired_keys: List(k)) -> Map(k, v) { - do_take(desired_keys, map) +@deprecated("Please use the `gleam/dict` module instead") +pub fn take(from map, keeping desired_keys: List(k)) { + dict.take(map, desired_keys) } -@target(erlang) -@external(erlang, "maps", "with") -fn do_take(a: List(k), b: Map(k, v)) -> Map(k, v) - -@target(javascript) -fn insert_taken( - map: Map(k, v), - desired_keys: List(k), - acc: Map(k, v), -) -> Map(k, v) { - let insert = fn(taken, key) { - case get(map, key) { - Ok(value) -> insert(taken, key, value) - _ -> taken - } - } - case desired_keys { - [] -> acc - [x, ..xs] -> insert_taken(map, xs, insert(acc, x)) - } +@deprecated("Please use the `gleam/dict` module instead") +pub fn merge(into map, from new_entries) { + dict.merge(map, new_entries) } -@target(javascript) -fn do_take(desired_keys: List(k), map: Map(k, v)) -> Map(k, v) { - insert_taken(map, desired_keys, new()) -} - -/// Creates a new map from a pair of given maps by combining their entries. -/// -/// If there are entries with the same keys in both maps the entry from the -/// second map takes precedence. -/// -/// ## Examples -/// -/// ```gleam -/// > let a = from_list([#("a", 0), #("b", 1)]) -/// > let b = from_list([#("b", 2), #("c", 3)]) -/// > merge(a, b) -/// from_list([#("a", 0), #("b", 2), #("c", 3)]) -/// ``` -/// -pub fn merge(into map: Map(k, v), from new_entries: Map(k, v)) -> Map(k, v) { - do_merge(map, new_entries) +@deprecated("Please use the `gleam/dict` module instead") +pub fn delete(from map, delete key: k) { + dict.delete(map, key) } -@target(erlang) -@external(erlang, "maps", "merge") -fn do_merge(a: Map(k, v), b: Map(k, v)) -> Map(k, v) - -@target(javascript) -fn insert_pair(map: Map(k, v), pair: #(k, v)) -> Map(k, v) { - insert(map, pair.0, pair.1) +@deprecated("Please use the `gleam/dict` module instead") +pub fn drop(from map, drop disallowed_keys: List(k)) { + dict.drop(map, disallowed_keys) } -@target(javascript) -fn fold_inserts(new_entries: List(#(k, v)), map: Map(k, v)) -> Map(k, v) { - case new_entries { - [] -> map - [x, ..xs] -> fold_inserts(xs, insert_pair(map, x)) - } +@deprecated("Please use the `gleam/dict` module instead") +pub fn update(in map, update key: k, with fun: fn(Option(v)) -> v) { + dict.update(map, key, fun) } -@target(javascript) -fn do_merge(map: Map(k, v), new_entries: Map(k, v)) -> Map(k, v) { - new_entries - |> to_list - |> fold_inserts(map) -} - -/// Creates a new map from a given map with all the same entries except for the -/// one with a given key, if it exists. -/// -/// ## Examples -/// -/// ```gleam -/// > delete([#("a", 0), #("b", 1)], "a") -/// from_list([#("b", 1)]) -/// ``` -/// -/// ```gleam -/// > delete([#("a", 0), #("b", 1)], "c") -/// from_list([#("a", 0), #("b", 1)]) -/// ``` -/// -pub fn delete(from map: Map(k, v), delete key: k) -> Map(k, v) { - do_delete(key, map) -} - -@external(erlang, "maps", "remove") -@external(javascript, "../gleam_stdlib.mjs", "map_remove") -fn do_delete(a: k, b: Map(k, v)) -> Map(k, v) - -/// Creates a new map from a given map with all the same entries except any with -/// keys found in a given list. -/// -/// ## Examples -/// -/// ```gleam -/// > drop([#("a", 0), #("b", 1)], ["a"]) -/// from_list([#("b", 2)]) -/// ``` -/// -/// ```gleam -/// > delete([#("a", 0), #("b", 1)], ["c"]) -/// from_list([#("a", 0), #("b", 1)]) -/// ``` -/// -/// ```gleam -/// > drop([#("a", 0), #("b", 1)], ["a", "b", "c"]) -/// from_list([]) -/// ``` -/// -pub fn drop(from map: Map(k, v), drop disallowed_keys: List(k)) -> Map(k, v) { - case disallowed_keys { - [] -> map - [x, ..xs] -> drop(delete(map, x), xs) - } -} - -/// Creates a new map with one entry updated using a given function. -/// -/// If there was not an entry in the map for the given key then the function -/// gets `None` as its argument, otherwise it gets `Some(value)`. -/// -/// ## Example -/// -/// ```gleam -/// > let increment = fn(x) { -/// > case x { -/// > Some(i) -> i + 1 -/// > None -> 0 -/// > } -/// > } -/// > let map = from_list([#("a", 0)]) -/// > -/// > update(map, "a", increment) -/// from_list([#("a", 1)]) -/// ``` -/// -/// ```gleam -/// > update(map, "b", increment) -/// from_list([#("a", 0), #("b", 0)]) -/// ``` -/// -pub fn update( - in map: Map(k, v), - update key: k, - with fun: fn(Option(v)) -> v, -) -> Map(k, v) { - map - |> get(key) - |> option.from_result - |> fun - |> insert(map, key, _) -} - -fn do_fold(list: List(#(k, v)), initial: acc, fun: fn(acc, k, v) -> acc) -> acc { - case list { - [] -> initial - [#(k, v), ..rest] -> do_fold(rest, fun(initial, k, v), fun) - } -} - -/// Combines all entries into a single value by calling a given function on each -/// one. -/// -/// Maps are not ordered so the values are not returned in any specific order. Do -/// not write code that relies on the order entries are used by this function -/// as it may change in later versions of Gleam or Erlang. -/// -/// # Examples -/// -/// ```gleam -/// > let map = from_list([#("a", 1), #("b", 3), #("c", 9)]) -/// > fold(map, 0, fn(accumulator, key, value) { accumulator + value }) -/// 13 -/// ``` -/// -/// ```gleam -/// > import gleam/string.{append} -/// > fold(map, "", fn(accumulator, key, value) { append(accumulator, key) }) -/// "abc" -/// ``` -/// -pub fn fold( - over map: Map(k, v), - from initial: acc, - with fun: fn(acc, k, v) -> acc, -) -> acc { - map - |> to_list - |> do_fold(initial, fun) +@deprecated("Please use the `gleam/dict` module instead") +pub fn fold(over map, from initial: acc, with fun: fn(acc, k, v) -> acc) -> acc { + dict.fold(map, initial, fun) } diff --git a/src/gleam/set.gleam b/src/gleam/set.gleam index 8e33e37..df8d500 100644 --- a/src/gleam/set.gleam +++ b/src/gleam/set.gleam @@ -1,5 +1,5 @@ import gleam/list -import gleam/map.{type Map} +import gleam/dict.{type Dict} import gleam/result // A list is used as the map value as an empty list has the smallest @@ -24,13 +24,13 @@ const token = Nil /// logarithmic time complexity. /// pub opaque type Set(member) { - Set(map: Map(member, Token)) + Set(map: Dict(member, Token)) } /// Creates a new empty set. /// pub fn new() -> Set(member) { - Set(map.new()) + Set(dict.new()) } /// Gets the number of members in a set. @@ -48,7 +48,7 @@ pub fn new() -> Set(member) { /// ``` /// pub fn size(set: Set(member)) -> Int { - map.size(set.map) + dict.size(set.map) } /// Inserts an member into the set. @@ -66,7 +66,7 @@ pub fn size(set: Set(member)) -> Int { /// ``` /// pub fn insert(into set: Set(member), this member: member) -> Set(member) { - Set(map: map.insert(set.map, member, token)) + Set(map: dict.insert(set.map, member, token)) } /// Checks whether a set contains a given member. @@ -91,7 +91,7 @@ pub fn insert(into set: Set(member), this member: member) -> Set(member) { /// pub fn contains(in set: Set(member), this member: member) -> Bool { set.map - |> map.get(member) + |> dict.get(member) |> result.is_ok } @@ -111,7 +111,7 @@ pub fn contains(in set: Set(member), this member: member) -> Bool { /// ``` /// pub fn delete(from set: Set(member), this member: member) -> Set(member) { - Set(map: map.delete(set.map, member)) + Set(map: dict.delete(set.map, member)) } /// Converts the set into a list of the contained members. @@ -129,7 +129,7 @@ pub fn delete(from set: Set(member), this member: member) -> Set(member) { /// ``` /// pub fn to_list(set: Set(member)) -> List(member) { - map.keys(set.map) + dict.keys(set.map) } /// Creates a new set of the members in a given list. @@ -148,8 +148,8 @@ pub fn from_list(members: List(member)) -> Set(member) { let map = list.fold( over: members, - from: map.new(), - with: fn(m, k) { map.insert(m, k, token) }, + from: dict.new(), + with: fn(m, k) { dict.insert(m, k, token) }, ) Set(map) } @@ -174,7 +174,7 @@ pub fn fold( from initial: acc, with reducer: fn(acc, member) -> acc, ) -> acc { - map.fold(over: set.map, from: initial, with: fn(a, k, _) { reducer(a, k) }) + dict.fold(over: set.map, from: initial, with: fn(a, k, _) { reducer(a, k) }) } /// Creates a new set from an existing set, minus any members that a given @@ -196,7 +196,7 @@ pub fn filter( in set: Set(member), keeping predicate: fn(member) -> Bool, ) -> Set(member) { - Set(map.filter(in: set.map, keeping: fn(m, _) { predicate(m) })) + Set(dict.filter(in: set.map, keeping: fn(m, _) { predicate(m) })) } pub fn drop(from set: Set(member), drop disallowed: List(member)) -> Set(member) { @@ -218,11 +218,11 @@ pub fn drop(from set: Set(member), drop disallowed: List(member)) -> Set(member) /// ``` /// pub fn take(from set: Set(member), keeping desired: List(member)) -> Set(member) { - Set(map.take(from: set.map, keeping: desired)) + Set(dict.take(from: set.map, keeping: desired)) } fn order(first: Set(member), second: Set(member)) -> #(Set(member), Set(member)) { - case map.size(first.map) > map.size(second.map) { + case dict.size(first.map) > dict.size(second.map) { True -> #(first, second) False -> #(second, first) } diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 7414439..c6ea125 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -378,7 +378,7 @@ inspect(Data) when is_map(Data) -> [<<"#(">>, inspect(Key), <<", ">>, inspect(Value), <<")">>] || {Key, Value} <- maps:to_list(Data) ], - ["map.from_list([", lists:join(", ", Fields), "])"]; + ["dict.from_list([", lists:join(", ", Fields), "])"]; inspect(Atom) when is_atom(Atom) -> Binary = erlang:atom_to_binary(Atom), case inspect_maybe_gleam_atom(Binary, none, <<>>) of diff --git a/test/gleam/dict_test.gleam b/test/gleam/dict_test.gleam new file mode 100644 index 0000000..9ab074b --- /dev/null +++ b/test/gleam/dict_test.gleam @@ -0,0 +1,393 @@ +import gleam/dict +import gleam/option.{None, Some} +import gleam/should +import gleam/string +import gleam/list +import gleam/int + +pub fn from_list_test() { + [#(4, 0), #(1, 0)] + |> dict.from_list + |> dict.size + |> should.equal(2) + + [#(1, 0), #(1, 1)] + |> dict.from_list + |> should.equal(dict.from_list([#(1, 1)])) +} + +pub fn has_key_test() { + [] + |> dict.from_list + |> dict.has_key(1) + |> should.be_false + + [#(1, 0)] + |> dict.from_list + |> dict.has_key(1) + |> should.be_true + + [#(4, 0), #(1, 0)] + |> dict.from_list + |> dict.has_key(1) + |> should.be_true + + [#(4, 0), #(1, 0)] + |> dict.from_list + |> dict.has_key(0) + |> should.be_false +} + +pub fn new_test() { + dict.new() + |> dict.size + |> should.equal(0) + + dict.new() + |> dict.to_list + |> should.equal([]) +} + +type Key { + A + B + C +} + +pub fn get_test() { + let proplist = [#(4, 0), #(1, 1)] + let m = dict.from_list(proplist) + + m + |> dict.get(4) + |> should.equal(Ok(0)) + + m + |> dict.get(1) + |> should.equal(Ok(1)) + + m + |> dict.get(2) + |> should.equal(Error(Nil)) + + let proplist = [#(A, 0), #(B, 1)] + let m = dict.from_list(proplist) + + m + |> dict.get(A) + |> should.equal(Ok(0)) + + m + |> dict.get(B) + |> should.equal(Ok(1)) + + m + |> dict.get(C) + |> should.equal(Error(Nil)) + + let proplist = [#(<<1, 2, 3>>, 0), #(<<3, 2, 1>>, 1)] + let m = dict.from_list(proplist) + + m + |> dict.get(<<1, 2, 3>>) + |> should.equal(Ok(0)) + + m + |> dict.get(<<3, 2, 1>>) + |> should.equal(Ok(1)) + + m + |> dict.get(<<1, 3, 2>>) + |> should.equal(Error(Nil)) +} + +pub fn insert_test() { + dict.new() + |> dict.insert("a", 0) + |> dict.insert("b", 1) + |> dict.insert("c", 2) + |> should.equal(dict.from_list([#("a", 0), #("b", 1), #("c", 2)])) +} + +pub fn map_values_test() { + [#(1, 0), #(2, 1), #(3, 2)] + |> dict.from_list + |> dict.map_values(fn(k, v) { k + v }) + |> should.equal(dict.from_list([#(1, 1), #(2, 3), #(3, 5)])) +} + +pub fn keys_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> dict.from_list + |> dict.keys + |> list.sort(string.compare) + |> should.equal(["a", "b", "c"]) +} + +pub fn values_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> dict.from_list + |> dict.values + |> list.sort(int.compare) + |> should.equal([0, 1, 2]) +} + +pub fn take_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> dict.from_list + |> dict.take(["a", "b", "d"]) + |> should.equal(dict.from_list([#("a", 0), #("b", 1)])) +} + +pub fn drop_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> dict.from_list + |> dict.drop(["a", "b", "d"]) + |> should.equal(dict.from_list([#("c", 2)])) +} + +pub fn merge_same_key_test() { + let a = dict.from_list([#("a", 2)]) + let b = dict.from_list([#("a", 0)]) + + dict.merge(a, b) + |> should.equal(dict.from_list([#("a", 0)])) + + dict.merge(b, a) + |> should.equal(dict.from_list([#("a", 2)])) +} + +pub fn merge_test() { + let a = dict.from_list([#("a", 2), #("c", 4), #("d", 3)]) + let b = dict.from_list([#("a", 0), #("b", 1), #("c", 2)]) + + dict.merge(a, b) + |> should.equal(dict.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)])) + + dict.merge(b, a) + |> should.equal(dict.from_list([#("a", 2), #("b", 1), #("c", 4), #("d", 3)])) +} + +pub fn delete_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> dict.from_list + |> dict.delete("a") + |> dict.delete("d") + |> should.equal(dict.from_list([#("b", 1), #("c", 2)])) +} + +pub fn update_test() { + let dict = dict.from_list([#("a", 0), #("b", 1), #("c", 2)]) + + let inc_or_zero = fn(x) { + case x { + Some(i) -> i + 1 + None -> 0 + } + } + + dict + |> dict.update("a", inc_or_zero) + |> should.equal(dict.from_list([#("a", 1), #("b", 1), #("c", 2)])) + + dict + |> dict.update("b", inc_or_zero) + |> should.equal(dict.from_list([#("a", 0), #("b", 2), #("c", 2)])) + + dict + |> dict.update("z", inc_or_zero) + |> should.equal(dict.from_list([#("a", 0), #("b", 1), #("c", 2), #("z", 0)])) +} + +pub fn fold_test() { + let dict = dict.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)]) + + let add = fn(acc, _, v) { v + acc } + + dict + |> dict.fold(0, add) + |> should.equal(6) + + let prepend = fn(acc, k, _) { list.prepend(acc, k) } + + dict + |> dict.fold([], prepend) + |> list.sort(string.compare) + |> should.equal(["a", "b", "c", "d"]) + + dict.from_list([]) + |> dict.fold(0, add) + |> should.equal(0) +} + +fn range(start, end, a) { + case end - start { + n if n < 1 -> a + _ -> range(start, end - 1, [end - 1, ..a]) + } +} + +fn list_to_map(list) { + list + |> list.map(fn(n) { #(n, n) }) + |> dict.from_list +} + +fn grow_and_shrink_map(initial_size, final_size) { + range(0, initial_size, []) + |> list_to_map + |> list.fold( + range(final_size, initial_size, []), + _, + fn(map, item) { dict.delete(map, item) }, + ) +} + +// maps should be equal even if the insert/removal order was different +pub fn insert_order_equality_test() { + grow_and_shrink_map(8, 2) + |> should.equal(grow_and_shrink_map(4, 2)) + grow_and_shrink_map(17, 10) + |> should.equal(grow_and_shrink_map(12, 10)) + grow_and_shrink_map(2000, 1000) + |> should.equal(grow_and_shrink_map(1000, 1000)) +} + +// ensure operations on a map don't mutate it +pub fn persistence_test() { + let a = list_to_map([0]) + dict.insert(a, 0, 5) + dict.insert(a, 1, 6) + dict.delete(a, 0) + dict.get(a, 0) + |> should.equal(Ok(0)) +} + +// using maps as keys should work (tests hash function) +pub fn map_as_key_test() { + let l = range(0, 1000, []) + let a = list_to_map(l) + let a2 = list_to_map(list.reverse(l)) + let a3 = grow_and_shrink_map(2000, 1000) + let b = grow_and_shrink_map(60, 50) + let c = grow_and_shrink_map(50, 20) + let d = grow_and_shrink_map(2, 2) + + let map1 = + dict.new() + |> dict.insert(a, "a") + |> dict.insert(b, "b") + |> dict.insert(c, "c") + |> dict.insert(d, "d") + + dict.get(map1, a) + |> should.equal(Ok("a")) + dict.get(map1, a2) + |> should.equal(Ok("a")) + dict.get(map1, a3) + |> should.equal(Ok("a")) + dict.get(map1, b) + |> should.equal(Ok("b")) + dict.get(map1, c) + |> should.equal(Ok("c")) + dict.get(map1, d) + |> should.equal(Ok("d")) + dict.insert(map1, a2, "a2") + |> dict.get(a) + |> should.equal(Ok("a2")) + dict.insert(map1, a3, "a3") + |> dict.get(a) + |> should.equal(Ok("a3")) +} + +pub fn large_n_test() { + let n = 10_000 + let l = range(0, n, []) + + let m = list_to_map(l) + list.map(l, fn(i) { should.equal(dict.get(m, i), Ok(i)) }) + + let m = grow_and_shrink_map(n, 0) + list.map(l, fn(i) { should.equal(dict.get(m, i), Error(Nil)) }) +} + +pub fn size_test() { + let n = 1000 + let m = list_to_map(range(0, n, [])) + dict.size(m) + |> should.equal(n) + + let m = grow_and_shrink_map(n, n / 2) + dict.size(m) + |> should.equal(n / 2) + + let m = + grow_and_shrink_map(n, 0) + |> dict.delete(0) + dict.size(m) + |> should.equal(0) + + let m = list_to_map(range(0, 18, [])) + + dict.insert(m, 1, 99) + |> dict.size() + |> should.equal(18) + dict.insert(m, 2, 99) + |> dict.size() + |> should.equal(18) +} + +// https://github.com/gleam-lang/stdlib/issues/435 +pub fn peters_bug_test() { + dict.new() + |> dict.insert(22, Nil) + |> dict.insert(21, Nil) + |> dict.insert(23, Nil) + |> dict.insert(18, Nil) + |> dict.insert(17, Nil) + |> dict.insert(19, Nil) + |> dict.insert(14, Nil) + |> dict.insert(13, Nil) + |> dict.insert(15, Nil) + |> dict.insert(10, Nil) + |> dict.insert(9, Nil) + |> dict.insert(11, Nil) + |> dict.insert(6, Nil) + |> dict.insert(5, Nil) + |> dict.insert(7, Nil) + |> dict.insert(2, Nil) + |> dict.insert(1, Nil) + |> dict.insert(3, Nil) + |> dict.get(0) + |> should.equal(Error(Nil)) +} + +pub fn zero_must_be_contained_test() { + let map = + dict.new() + |> dict.insert(0, Nil) + + map + |> dict.get(0) + |> should.equal(Ok(Nil)) + + map + |> dict.has_key(0) + |> should.equal(True) +} + +pub fn empty_map_equality_test() { + let map1 = dict.new() + let map2 = dict.from_list([#(1, 2)]) + + should.be_false(map1 == map2) + should.be_false(map2 == map1) +} + +pub fn extra_keys_equality_test() { + let map1 = dict.from_list([#(1, 2), #(3, 4)]) + let map2 = dict.from_list([#(1, 2), #(3, 4), #(4, 5)]) + + should.be_false(map1 == map2) + should.be_false(map2 == map1) +} diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index 06941f3..c072756 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -1,5 +1,5 @@ import gleam/dynamic.{DecodeError} -import gleam/map +import gleam/dict import gleam/option.{None, Some} import gleam/result import gleam/should @@ -293,34 +293,34 @@ pub fn javascript_object_field_test() { } pub fn field_test() { - map.new() - |> map.insert("ok", 1) + dict.new() + |> dict.insert("ok", 1) |> dynamic.from |> dynamic.field(named: "ok", of: dynamic.int) |> should.equal(Ok(1)) - map.new() - |> map.insert("ok", 1.0) + dict.new() + |> dict.insert("ok", 1.0) |> dynamic.from |> dynamic.field(named: "ok", of: dynamic.float) |> should.equal(Ok(1.0)) - map.new() - |> map.insert("ok", 3) - |> map.insert("error", 1) + dict.new() + |> dict.insert("ok", 3) + |> dict.insert("error", 1) |> dynamic.from |> dynamic.field("ok", dynamic.int) |> should.equal(Ok(3)) - map.new() - |> map.insert("ok", 3) + dict.new() + |> dict.insert("ok", 3) |> dynamic.from |> dynamic.field("ok", dynamic.string) |> should.equal(Error([ DecodeError(expected: "String", found: "Int", path: ["ok"]), ])) - map.new() + dict.new() |> dynamic.from |> dynamic.field("ok", dynamic.int) |> should.equal(Error([ @@ -337,8 +337,8 @@ pub fn field_test() { |> dynamic.field("ok", dynamic.int) |> should.equal(Error([DecodeError(expected: "Map", found: "List", path: [])])) - map.new() - |> map.insert("ok", 1) + dict.new() + |> dict.insert("ok", 1) |> dynamic.from |> dynamic.field("ok", dynamic.field("not_a_field", dynamic.int)) |> should.equal(Error([ @@ -347,46 +347,46 @@ pub fn field_test() { } pub fn optional_field_test() { - map.new() - |> map.insert("ok", 1) + dict.new() + |> dict.insert("ok", 1) |> dynamic.from |> dynamic.optional_field(named: "ok", of: dynamic.int) |> should.equal(Ok(Some(1))) - map.new() - |> map.insert("ok", 1.0) + dict.new() + |> dict.insert("ok", 1.0) |> dynamic.from |> dynamic.optional_field(named: "ok", of: dynamic.float) |> should.equal(Ok(Some(1.0))) - map.new() - |> map.insert("ok", 3) - |> map.insert("error", 1) + dict.new() + |> dict.insert("ok", 3) + |> dict.insert("error", 1) |> dynamic.from |> dynamic.optional_field("ok", dynamic.int) |> should.equal(Ok(Some(3))) - map.new() - |> map.insert("ok", 3) + dict.new() + |> dict.insert("ok", 3) |> dynamic.from |> dynamic.optional_field("ok", dynamic.string) |> should.equal(Error([ DecodeError(expected: "String", found: "Int", path: ["ok"]), ])) - map.new() - |> map.insert("ok", None) + dict.new() + |> dict.insert("ok", None) |> dynamic.from |> dynamic.optional_field("ok", dynamic.int) |> should.equal(Ok(None)) - map.new() - |> map.insert("ok", Nil) + dict.new() + |> dict.insert("ok", Nil) |> dynamic.from |> dynamic.optional_field("ok", dynamic.int) |> should.equal(Ok(None)) - map.new() + dict.new() |> dynamic.from |> dynamic.optional_field("ok", dynamic.int) |> should.equal(Ok(None)) @@ -459,8 +459,8 @@ pub fn element_test() { |> dynamic.element(0, dynamic.int) |> should.equal(Error([DecodeError(expected: "Tuple", found: "Int", path: [])])) - map.new() - |> map.insert(1, "ok") + dict.new() + |> dict.insert(1, "ok") |> dynamic.from |> dynamic.element(0, dynamic.int) |> should.equal(Error([DecodeError(expected: "Tuple", found: "Map", path: [])])) @@ -999,24 +999,24 @@ pub fn nested_tuples_test() { } pub fn map_test() { - map.new() + dict.new() |> dynamic.from |> dynamic.map(dynamic.string, dynamic.int) - |> should.equal(Ok(map.new())) + |> should.equal(Ok(dict.new())) - map.from_list([#("a", 1), #("b", 2)]) + dict.from_list([#("a", 1), #("b", 2)]) |> dynamic.from |> dynamic.map(dynamic.string, dynamic.int) - |> should.equal(Ok(map.from_list([#("a", 1), #("b", 2)]))) + |> should.equal(Ok(dict.from_list([#("a", 1), #("b", 2)]))) - map.from_list([#("a", 1), #("b", 2)]) + dict.from_list([#("a", 1), #("b", 2)]) |> dynamic.from |> dynamic.map(dynamic.int, dynamic.int) |> should.equal(Error([ DecodeError(expected: "Int", found: "String", path: ["keys"]), ])) - map.from_list([#("a", 1), #("b", 2)]) + dict.from_list([#("a", 1), #("b", 2)]) |> dynamic.from |> dynamic.map(dynamic.string, dynamic.string) |> should.equal(Error([ diff --git a/test/gleam/iterator_test.gleam b/test/gleam/iterator_test.gleam index adb4802..a83decc 100644 --- a/test/gleam/iterator_test.gleam +++ b/test/gleam/iterator_test.gleam @@ -1,6 +1,6 @@ import gleam/iterator.{Done, Next} import gleam/list -import gleam/map +import gleam/dict import gleam/should // a |> from_list |> to_list == a @@ -480,7 +480,7 @@ pub fn all_test() { pub fn group_test() { iterator.from_list([1, 2, 3, 4, 5, 6]) |> iterator.group(by: fn(n) { n % 3 }) - |> should.equal(map.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])])) + |> should.equal(dict.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])])) } pub fn reduce_test() { diff --git a/test/gleam/list_test.gleam b/test/gleam/list_test.gleam index ae066ad..0bebe2f 100644 --- a/test/gleam/list_test.gleam +++ b/test/gleam/list_test.gleam @@ -2,7 +2,7 @@ import gleam/float import gleam/pair import gleam/int import gleam/list -import gleam/map +import gleam/dict import gleam/should @target(erlang) @@ -103,17 +103,17 @@ pub fn group_test() { } }) |> should.equal( - map.new() - |> map.insert("Failed", [Error("Wrong")]) - |> map.insert("Successful", [Ok(1), Ok(200), Ok(10)]), + dict.new() + |> dict.insert("Failed", [Error("Wrong")]) + |> dict.insert("Successful", [Ok(1), Ok(200), Ok(10)]), ) list.group([1, 2, 3, 4, 5], fn(i) { i - i / 3 * 3 }) |> should.equal( - map.new() - |> map.insert(0, [3]) - |> map.insert(1, [4, 1]) - |> map.insert(2, [5, 2]), + dict.new() + |> dict.insert(0, [3]) + |> dict.insert(1, [4, 1]) + |> dict.insert(2, [5, 2]), ) } diff --git a/test/gleam/string_test.gleam b/test/gleam/string_test.gleam index 19341d0..bb3f502 100644 --- a/test/gleam/string_test.gleam +++ b/test/gleam/string_test.gleam @@ -1,5 +1,5 @@ import gleam/option.{None, Some} -import gleam/map +import gleam/dict import gleam/order import gleam/should import gleam/string @@ -1089,7 +1089,7 @@ pub fn byte_size_test() { } pub fn inspect_map_test() { - map.from_list([#("a", 1), #("b", 2)]) + dict.from_list([#("a", 1), #("b", 2)]) |> string.inspect - |> should.equal("map.from_list([#(\"a\", 1), #(\"b\", 2)])") + |> should.equal("dict.from_list([#(\"a\", 1), #(\"b\", 2)])") } |