aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/gleam/dict.gleam48
-rw-r--r--test/gleam/dict_test.gleam26
3 files changed, 64 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f7b3cb..42c5a4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
correctly on Erlang.
- `dynamic.unsafe_coerce` function has been deprecated.
- Fixed `bit_array` slices of slices sometimes being incorrect on JavaScript.
+- The `dict` module gains the `combine` function.
## v0.37.0 - 2024-04-19
diff --git a/src/gleam/dict.gleam b/src/gleam/dict.gleam
index 6013f32..f44daa3 100644
--- a/src/gleam/dict.gleam
+++ b/src/gleam/dict.gleam
@@ -43,13 +43,13 @@ pub fn size(dict: Dict(k, v)) -> Int
/// ## Examples
///
/// Calling `to_list` on an empty `dict` returns an empty list.
-///
+///
/// ```gleam
/// new() |> to_list
/// // -> []
/// ```
-///
-/// The ordering of elements in the resulting list is an implementation detail
+///
+/// The ordering of elements in the resulting list is an implementation detail
/// that should not be relied upon.
///
/// ```gleam
@@ -498,17 +498,17 @@ pub fn fold(
|> do_fold(initial, fun)
}
-/// Calls a function for each key and value in a dict, discarding the return
+/// Calls a function for each key and value in a dict, discarding the return
/// value.
-///
+///
/// Useful for producing a side effect for every item of a dict.
-///
+///
/// ```gleam
/// import gleam/io
-///
+///
/// let dict = from_list([#("a", "apple"), #("b", "banana"), #("c", "cherry")])
-///
-/// each(dict, fn(key, value) {
+///
+/// each(dict, fn(key, value) {
/// io.println(key <> " => " <> value)
/// })
/// // -> Nil
@@ -516,13 +516,39 @@ pub fn fold(
/// // b => banana
/// // c => cherry
/// ```
-///
+///
/// The order of elements in the iteration is an implementation detail that
/// should not be relied upon.
-///
+///
pub fn each(dict: Dict(k, v), fun: fn(k, v) -> b) -> Nil {
fold(dict, Nil, fn(nil, k, v) {
fun(k, v)
nil
})
}
+
+/// Creates a new dict from a pair of given dicts by combining their entries.
+///
+/// If there are entries with the same keys in both dicts the given function is
+/// used to determine the new value to use in the resulting dict.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let a = from_list([#("a", 0), #("b", 1)])
+/// let b = from_list([#("a", 2), #("c", 3)])
+/// combine(a, b, fn(one, other) { one + other })
+/// // -> from_list([#("a", 2), #("b", 1), #("c", 3)])
+/// ```
+///
+pub fn combine(
+ dict: Dict(k, v),
+ other: Dict(k, v),
+ with fun: fn(v, v) -> v,
+) -> Dict(k, v) {
+ use acc, key, value <- fold(over: dict, from: other)
+ case get(acc, key) {
+ Ok(other_value) -> insert(acc, key, fun(value, other_value))
+ Error(_) -> insert(acc, key, value)
+ }
+}
diff --git a/test/gleam/dict_test.gleam b/test/gleam/dict_test.gleam
index ca77bb9..0bcb334 100644
--- a/test/gleam/dict_test.gleam
+++ b/test/gleam/dict_test.gleam
@@ -403,3 +403,29 @@ pub fn extra_keys_equality_test() {
should.be_false(map1 == map2)
should.be_false(map2 == map1)
}
+
+pub fn combine_test() {
+ let map1 = dict.from_list([#("a", 3), #("b", 2)])
+ let map2 = dict.from_list([#("a", 2), #("c", 3), #("d", 4)])
+
+ dict.combine(map1, map2, fn(one, other) { one - other })
+ |> should.equal(dict.from_list([#("a", 1), #("b", 2), #("c", 3), #("d", 4)]))
+}
+
+pub fn combine_with_empty_test() {
+ let map1 = dict.from_list([#("a", 3), #("b", 2)])
+
+ dict.combine(map1, dict.new(), fn(one, _) { one })
+ |> should.equal(map1)
+
+ dict.combine(dict.new(), map1, fn(one, _) { one })
+ |> should.equal(map1)
+}
+
+pub fn combine_with_no_overlapping_keys_test() {
+ let map1 = dict.from_list([#("a", 1), #("b", 2)])
+ let map2 = dict.from_list([#("c", 3), #("d", 4)])
+
+ dict.combine(map1, map2, fn(one, _) { one })
+ |> should.equal(dict.from_list([#("a", 1), #("b", 2), #("c", 3), #("d", 4)]))
+}