From 4dc6672885c854b9cdc31cec81aed6fd01b18543 Mon Sep 17 00:00:00 2001 From: microproofs Date: Mon, 29 Apr 2024 12:23:37 -0400 Subject: [PATCH] upload new map.ak file and functions and tests. This invariant is like dict with keys that can be any type and ordering is unspecified. --- lib/aiken/dict.ak | 7 +- lib/aiken/map.ak | 814 +++++++++++++++++++++++++++++++++++++ lib/aiken/math/rational.ak | 2 +- 3 files changed, 819 insertions(+), 4 deletions(-) create mode 100644 lib/aiken/map.ak diff --git a/lib/aiken/dict.ak b/lib/aiken/dict.ak index 3bf918f..5edf9a1 100644 --- a/lib/aiken/dict.ak +++ b/lib/aiken/dict.ak @@ -23,7 +23,7 @@ pub opaque type Dict { inner: Map, } -/// Create a new map +/// Create a new dict /// ```aiken /// dict.to_map(dict.new()) == [] /// ``` @@ -194,7 +194,7 @@ test filter_3() { /// |> dict.insert(key: "c", value: 42) /// |> dict.find(42) /// -/// result == Some("c") +/// result == Some("a") /// ``` pub fn find(self: Dict, value v: value) -> Option { do_find(self.inner, v) @@ -686,7 +686,8 @@ test insert_2() { } /// Insert a value in the dictionary at a given key. When the key already exist, the provided -/// merge function is called. +/// merge function is called. The value existing in the dictionary is passed as the second argument +/// to the merge function, and the new value is passed as the third argument. /// /// ```aiken /// let sum = diff --git a/lib/aiken/map.ak b/lib/aiken/map.ak new file mode 100644 index 0000000..951104c --- /dev/null +++ b/lib/aiken/map.ak @@ -0,0 +1,814 @@ +//// A module for working with Map Data. +//// +//// These Maps are non-ordered lists of key-value pairs, +//// which preserve no invariant on the order of the keys or the duplication of keys. + +use aiken/list + +const foo = #"666f6f" + +const bar = #"626172" + +const baz = #"62617a" + +fn fixture_1() { + [] + |> insert(foo, 42) + |> insert(bar, 14) +} + +/// Remove a single key-value pair from the Map. If the key is not found, no changes are made. +/// Duplicate keys are not removed. Only the first key found is removed. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: 100) +/// |> map.insert(key: "b", value: 200) +/// |> map.remove_first(key: "a") +/// +/// result == [Pair("b", 200)] +/// ``` +pub fn remove_first(self: Map, key k: key) -> Map { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + rest + } else { + [Pair(k2, v2), ..remove_first(rest, k)] + } + } +} + +/// Remove all key-value pairs matching the key from the Map. If the key is not found, no changes are made. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: 100) +/// |> map.insert(key: "a", value: 200) +/// |> map.remove_all(key: "a") +/// +/// result == [] +/// ``` +pub fn remove_all(self: Map, key k: key) -> Map { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + remove_all(rest, k) + } else { + [Pair(k2, v2), ..remove_all(rest, k)] + } + } +} + +test remove_first_1() { + remove_first([], foo) == [] +} + +test remove_first_2() { + let m = + [] + |> insert(foo, 14) + remove_first(m, foo) == [] +} + +test remove_first_3() { + let m = + [] + |> insert(foo, 14) + remove_first(m, bar) == m +} + +test remove_first_4() { + let m = + [] + |> insert(foo, 14) + |> insert(bar, 14) + !has_key(remove_first(m, foo), foo) +} + +test remove_first_5() { + let m = + [] + |> insert(foo, 14) + |> insert(bar, 14) + has_key(remove_first(m, bar), foo) +} + +test remove_first_6() { + let m = + [] + |> insert("aaa", 1) + |> insert("bbb", 2) + |> insert("ccc", 3) + |> insert("ddd", 4) + |> insert("eee", 5) + |> insert("fff", 6) + |> insert("ggg", 7) + |> insert("hhh", 8) + |> insert("iii", 9) + |> insert("jjj", 10) + + remove_first(m, "bcd") == m +} + +/// Keep only the key-value pairs that pass the given predicate. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: 100) +/// |> map.insert(key: "b", value: 200) +/// |> map.insert(key: "c", value: 300) +/// |> map.filter(fn(k, _v) { k != "a" }) +/// +/// result == [Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn filter( + self: Map, + with: fn(key, value) -> Bool, +) -> Map { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + if with(k, v) { + [Pair(k, v), ..filter(rest, with)] + } else { + filter(rest, with) + } + } +} + +test filter_1() { + filter([], fn(_, _) { True }) == [] +} + +test filter_2() { + let expected = + [] + |> insert(foo, 42) + filter(fixture_1(), fn(_, v) { v > 14 }) == expected +} + +test filter_3() { + let expected = + [] + |> insert(bar, 14) + filter(fixture_1(), fn(k, _) { k == bar }) == expected +} + +/// Finds a value in the map, and returns the first key found to have that value. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: 42) +/// |> map.insert(key: "b", value: 14) +/// |> map.insert(key: "c", value: 42) +/// |> map.find(42) +/// +/// result == Some("a") +/// ``` +pub fn find(self: Map, v: value) -> Option { + when self is { + [] -> None + [Pair(k2, v2), ..rest] -> + if v == v2 { + Some(k2) + } else { + find(rest, v) + } + } +} + +test find_1() { + find([], foo) == None +} + +test find_2() { + find( + [] + |> insert(foo, 14), + 14, + ) == Some(foo) +} + +test find_3() { + find( + [] + |> insert(foo, 14), + 42, + ) == None +} + +test find_4() { + find( + [] + |> insert(foo, 14) + |> insert(bar, 42) + |> insert(baz, 14), + 14, + ) == Some(foo) +} + +/// Fold over the key-value pairs in a map. The fold direction follows keys +/// in ascending order and is done from right-to-left. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: 100) +/// |> map.insert(key: "b", value: 200) +/// |> map.insert(key: "c", value: 300) +/// |> map.foldr(0, fn(_k, v, r) { v + r }) +/// +/// result == 600 +/// ``` +pub fn foldr( + self: Map, + zero: result, + with: fn(key, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> with(k, v, foldr(rest, zero, with)) + } +} + +test foldr_1() { + foldr([], 14, fn(_, _, _) { 42 }) == 14 +} + +test foldr_2() { + foldr(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56 +} + +/// Fold over the key-value pairs in a map. The fold direction follows keys +/// in ascending order and is done from left-to-right. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: 100) +/// |> map.insert(key: "b", value: 200) +/// |> map.insert(key: "c", value: 300) +/// |> map.foldl(0, fn(_k, v, r) { v + r }) +/// +/// result == 600 +/// ``` +pub fn foldl( + self: Map, + zero: result, + with: fn(key, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> foldl(rest, with(k, v, zero), with) + } +} + +test fold_1() { + foldl([], 14, fn(_, _, _) { 42 }) == 14 +} + +test fold_2() { + foldl(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56 +} + +/// Get the value in the map by its key. +/// If multiple values with the same key exist, the first one is returned. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: "Aiken") +/// |> map.get_first(key: "a") +/// +/// result == Some("Aiken") +/// ``` +pub fn get_first(self: Map, key k: key) -> Option { + when self is { + [] -> None + [Pair(k2, v), ..rest] -> + if k == k2 { + Some(v) + } else { + get_first(rest, k) + } + } +} + +/// Get all values in the map by a key. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: "Aiken") +/// |> map.insert(key: "a", value: "Aiken2") +/// |> map.get_all(key: "a") +/// +/// result == ["Aiken", "Aiken2"] +/// ``` +pub fn get_all(self: Map, key k: key) -> List { + when self is { + [] -> + [] + [Pair(k2, v), ..rest] -> + if k == k2 { + [v, ..get_all(rest, k)] + } else { + get_all(rest, k) + } + } +} + +test get_1() { + get_first([], foo) == None +} + +test get_2() { + let m = + [] + |> insert(foo, "Aiken") + |> insert(bar, "awesome") + get_first(m, key: foo) == Some("Aiken") +} + +test get_3() { + let m = + [] + |> insert(foo, "Aiken") + |> insert(bar, "awesome") + get_first(m, key: baz) == None +} + +test get_4() { + let m = + [] + |> insert("aaa", "1") + |> insert("bbb", "2") + |> insert("ccc", "3") + |> insert("ddd", "4") + |> insert("eee", "5") + |> insert("fff", "6") + |> insert("ggg", "7") + |> insert("hhh", "8") + |> insert("iii", "9") + |> insert("jjj", "10") + + get_first(m, "bcd") == None +} + +test get_5() { + let m = + [] + |> insert("aaa", "1") + |> insert("bbb", "2") + |> insert("ccc", "3") + |> insert("ddd", "4") + |> insert("eee", "5") + |> insert("fff", "6") + |> insert("ggg", "7") + |> insert("hhh", "8") + |> insert("iii", "9") + |> insert("jjj", "10") + + get_first(m, "kkk") == None +} + +/// Check if a key exists in the map. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: "Aiken") +/// |> map.has_key("a") +/// +/// result == True +/// ``` +pub fn has_key(self: Map, k: key) -> Bool { + when self is { + [] -> False + // || is lazy so this is fine + [Pair(k2, _), ..rest] -> k == k2 || has_key(rest, k) + } +} + +test has_key_1() { + !has_key([], foo) +} + +test has_key_2() { + has_key( + [] + |> insert(foo, 14), + foo, + ) +} + +test has_key_3() { + !has_key( + [] + |> insert(foo, 14), + bar, + ) +} + +test has_key_4() { + has_key( + [] + |> insert(foo, 14) + |> insert(bar, 42), + bar, + ) +} + +/// Insert a value in the map at a given key. If the key already exists, its value is **overridden**. If you need ways to combine keys together, use (`insert_with`)[#insert_with]. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert(key: "a", value: 1) +/// |> map.insert(key: "b", value: 2) +/// |> map.insert(key: "a", value: 3) +/// +/// result == [Pair("a", 3), Pair("b", 2)] +/// ``` +pub fn insert( + self: Map, + key k: key, + value v: value, +) -> Map { + when self is { + [] -> + [Pair(k, v)] + [Pair(k2, v2), ..rest] -> + if k == k2 { + [Pair(k, v), ..rest] + } else { + [Pair(k2, v2), ..insert(rest, k, v)] + } + } +} + +test insert_1() { + let m1 = + [] + |> insert(foo, 42) + let m2 = + [] + |> insert(foo, 14) + insert(m1, foo, 14) == m2 +} + +test insert_2() { + let m1 = + [] + |> insert(foo, 42) + let m2 = + [] + |> insert(bar, 14) + + insert(m1, bar, 14) == ( insert(m2, foo, 42) |> list.reverse() ) +} + +/// Insert a value in the map at a given key. When the key already exist, the provided +/// merge function is called. The value existing in the map is passed as the second argument +/// to the merge function, and the new value is passed as the third argument. +/// +/// ```aiken +/// let sum = +/// fn (_k, a, b) { Some(a + b) } +/// +/// let result = +/// [] +/// |> map.insert_with(key: "a", value: 1, with: sum) +/// |> map.insert_with(key: "b", value: 2, with: sum) +/// |> map.insert_with(key: "a", value: 3, with: sum) +/// +/// result == [Pair("a", 4), Pair("b", 2)] +/// ``` +pub fn insert_with( + self: Map, + key k: key, + value v: value, + with: fn(key, value, value) -> Option, +) -> Map { + when self is { + [] -> + [Pair(k, v)] + [Pair(k2, v2), ..rest] -> + if k == k2 { + when with(k, v2, v) is { + Some(combined) -> + [Pair(k, combined), ..rest] + None -> rest + } + } else { + [Pair(k2, v2), ..insert_with(rest, k, v, with)] + } + } +} + +test insert_with_1() { + let sum = + fn(_k, a, b) { Some(a + b) } + + let result = + [] + |> insert_with(key: "foo", value: 1, with: sum) + |> insert_with(key: "bar", value: 2, with: sum) + + result == [Pair("foo", 1), Pair("bar", 2)] +} + +test insert_with_2() { + let sum = + fn(_k, a, b) { Some(a + b) } + + let result = + [] + |> insert_with(key: "foo", value: 1, with: sum) + |> insert_with(key: "bar", value: 2, with: sum) + |> insert_with(key: "foo", value: 3, with: sum) + + result == [Pair("foo", 4), Pair("bar", 2)] +} + +test insert_with_3() { + let with = + fn(k, a, _b) { + if k == "foo" { + Some(a) + } else { + None + } + } + + let result = + [] + |> insert_with(key: "foo", value: 1, with: with) + |> insert_with(key: "bar", value: 2, with: with) + |> insert_with(key: "foo", value: 3, with: with) + |> insert_with(key: "bar", value: 4, with: with) + + result == [Pair("foo", 1)] +} + +/// Efficiently checks whether a map is empty. +/// ```aiken +/// map.is_empty([]) == True +/// ``` +pub fn is_empty(self: Map) -> Bool { + when self is { + [] -> True + _ -> False + } +} + +test is_empty_1() { + is_empty([]) +} + +/// Extract all the keys present in a given `Map`. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert("a", 14) +/// |> map.insert("b", 42) +/// |> map.insert("a", 1337) +/// |> map.keys() +/// +/// result == ["a", "b"] +/// ``` +pub fn keys(self: Map) -> List { + when self is { + [] -> + [] + [Pair(k, _), ..rest] -> + [k, ..keys(rest)] + } +} + +test keys_1() { + keys([]) == [] +} + +test keys_2() { + keys( + [] + |> insert(foo, 0) + |> insert(bar, 0), + ) == [foo, bar] +} + +/// Apply a function to all key-value pairs in a map. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert("a", 100) +/// |> map.insert("b", 200) +/// |> map.insert("c", 300) +/// |> map.map(fn(_k, v) { v * 2 }) +/// +/// result == [Pair("a", 200), Pair("b", 400), Pair("c", 600)] +/// ``` +pub fn map(self: Map, with: fn(key, a) -> b) -> Map { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + [Pair(k, with(k, v)), ..map(rest, with)] + } +} + +test map_1() { + let result = + fixture_1() + |> map(with: fn(k, _) { k }) + get_first(result, foo) == Some(foo) +} + +test map_2() { + let result = + fixture_1() + |> map(with: fn(_, v) { v + 1 }) + get_first(result, foo) == Some(43) && size(result) == size(fixture_1()) +} + +/// Return the number of key-value pairs in the map. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert("a", 100) +/// |> map.insert("b", 200) +/// |> map.insert("c", 300) +/// |> map.size() +/// +/// result == 3 +/// ``` +pub fn size(self: Map) -> Int { + when self is { + [] -> 0 + [_, ..rest] -> 1 + size(rest) + } +} + +test size_1() { + size([]) == 0 +} + +test size_2() { + size( + [] + |> insert(foo, 14), + ) == 1 +} + +test size_3() { + size( + [] + |> insert(foo, 14) + |> insert(bar, 42), + ) == 2 +} + +/// Combine two map. If the same key exist in both the left and +/// right map, values from the left are preferred (i.e. left-biaised). +/// Ordering from the right is preferred with items from the left added after if not present. +/// +/// ```aiken +/// let left_map = [Pair("a", 100), Pair("b", 200)] +/// let right_map = [Pair("a", 150), Pair("c", 300)] +/// +/// let result = +/// map.union(left_map, right_map) +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn union(left: Map, right: Map) -> Map { + when left is { + [] -> right + [Pair(k, v), ..rest] -> union(rest, insert(right, k, v)) + } +} + +test union_1() { + union(fixture_1(), []) == fixture_1() +} + +test union_2() { + union([], fixture_1()) == fixture_1() +} + +test union_3() { + let left = + [] + |> insert(foo, 14) + + let right = + [] + |> insert(bar, 42) + |> insert(baz, 1337) + union(left, right) == [Pair(bar, 42), Pair(baz, 1337), Pair(foo, 14)] +} + +test union_4() { + let left = + [] + |> insert(foo, 14) + let right = + [] + |> insert(bar, 42) + |> insert(foo, 1337) + union(left, right) == [Pair(bar, 42), Pair(foo, 14)] +} + +/// Like [`union`](#union) but allows specifying the behavior to adopt when a key is present +/// in both map. The first value received correspond to the value in the left +/// map, whereas the second argument corresponds to the value in the right map. +/// +/// When passing `None`, the value is removed and not present in the union. +/// Ordering from the right is preferred with items from the left added after if not present. +/// +/// ```aiken +/// let left_map = [Pair("a", 100), Pair("b", 200)] +/// let right_map = [Pair("a", 150), Pair("c", 300)] +/// +/// let result = +/// map.union_with( +/// left_map, +/// right_map, +/// fn(_k, v1, v2) { Some(v1 + v2) }, +/// ) +/// +/// result == [Pair("a", 250), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn union_with( + left: Map, + right: Map, + with: fn(key, value, value) -> Option, +) -> Map { + when left is { + [] -> right + [Pair(k, v), ..rest] -> + union_with(rest, insert_with(right, k, v, with), with) + } +} + +test union_with_1() { + let left = + [] + |> insert(foo, 14) + + let right = + [] + |> insert(bar, 42) + |> insert(foo, 1337) + + let result = union_with(left, right, with: fn(_, l, r) { Some(l + r) }) + + result == [Pair(bar, 42), Pair(foo, 1351)] +} + +/// Extract all the values present in a given `Map`. +/// +/// ```aiken +/// let result = +/// [] +/// |> map.insert("a", 14) +/// |> map.insert("b", 42) +/// |> map.insert("c", 1337) +/// |> map.values() +/// +/// result == [1337, 42] +/// ``` +pub fn values(self: Map) -> List { + when self is { + [] -> + [] + [Pair(_, v), ..rest] -> + [v, ..values(rest)] + } +} + +test values_1() { + values([]) == [] +} + +test values_2() { + values( + [] + |> insert(foo, 3) + |> insert(bar, 4), + ) == [3, 4] +} diff --git a/lib/aiken/math/rational.ak b/lib/aiken/math/rational.ak index e62a65d..6bca643 100644 --- a/lib/aiken/math/rational.ak +++ b/lib/aiken/math/rational.ak @@ -334,7 +334,7 @@ test ceil_1() { } } -/// Returns the proper fraction of a given `Rational` `r`. That is, a pair of +/// Returns the proper fraction of a given `Rational` `r`. That is, a 2-tuple of /// an `Int` and `Rational` (n, f) such that: /// /// - `r = n + f`;