Skip to content

Commit

Permalink
rename 'merge_by_ascending_key' into 'insert_with_by_ascending_key'
Browse files Browse the repository at this point in the history
  Keep consistency with naming used in dict and list.
  • Loading branch information
KtorZ committed Oct 2, 2024
1 parent 238832a commit 2b54b43
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 66 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v2.2.0 - UNRELEASED

### Added

- [`aiken/collection/pairs.{insert_with_by_ascending_key}`](https://aiken-lang.github.io/stdlib/aiken/collection/pairs.html#insert_with_by_ascending_key): for inserting in pairs while specifying how to combine values on key conflict.

## v2.1.0 - 2024-09-14

### Added
Expand Down
88 changes: 88 additions & 0 deletions lib/aiken/collection/list.ak
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,94 @@ pub fn foldl(self: List<a>, zero: b, with: fn(a, b) -> b) -> b {
}
}

type Fold2<a, b, result> =
fn(a, b) -> result

pub fn foldl2(
self: List<elem>,
zero_a: a,
zero_b: b,
with: fn(elem, a, b, Fold2<a, b, result>) -> result,
return: Fold2<a, b, result>,
) -> result {
do_foldl2(self, with, return)(zero_a, zero_b)
}

fn do_foldl2(
self: List<elem>,
with: fn(elem, a, b, Fold2<a, b, result>) -> result,
return: Fold2<a, b, result>,
) -> Fold2<a, b, result> {
when self is {
[] -> return
[x, ..xs] -> do_foldl2(xs, with, fn(a, b) { with(x, a, b, return) })
}
}

test foldl2_optimized() {
let
len,
sum,
<-
foldl2(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
0,
0,
fn(n, len, sum, return) { return(len + 1, sum + n) },
)

and {
len == 10,
sum == 55,
}
}

test foldl2_classic() {
let (len, sum) =
foldl(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
(0, 0),
fn(n, (len, sum)) { (len + 1, sum + n) },
)

and {
len == 10,
sum == 55,
}
}

type Foo {
Foo(Int, Int)
}

test foldl2_pair() {
let Pair(len, sum) =
foldl(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
Pair(0, 0),
fn(n, Pair(len, sum)) { Pair(len + 1, sum + n) },
)

and {
len == 10,
sum == 55,
}
}

test foldl2_foo() {
let Foo(len, sum) =
foldl(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
Foo(0, 0),
fn(n, Foo(len, sum)) { Foo(len + 1, sum + n) },
)

and {
len == 10,
sum == 55,
}
}

test foldl_1() {
foldl([], 0, fn(_, _) { 1 }) == 0
}
Expand Down
144 changes: 78 additions & 66 deletions lib/aiken/collection/pairs.ak
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ test delete_last_4() {
/// Insert a value in the `Pairs` at a given key. If the key already exists,
/// the value is added in front.
///
/// > [!CAUTION]
/// > The list is only traversed up to the given key and the traversal
/// > stops as soon as a higher key is encountered. Said differently, the list
/// > is assumed to **be ordered by ascending keys**! If it is not, expect the
/// > unexpected.
///
/// ```aiken
/// use aiken/primitive/bytearray
///
Expand Down Expand Up @@ -524,97 +530,68 @@ test insert_by_ascending_key_2() {
m == [Pair("bar", 14), Pair("baz", 1337), Pair("foo", 42)]
}

/// Apply a function to all key-value pairs in a alist, replacing the values.
///
/// ```aiken
/// let fixture = [Pair("a", 100), Pair("b", 200)]
/// Like [`insert_by_ascending_key`](#insert_by_ascending_key) but specifies
/// how to combine two values on a key conflict.
///
/// pairs.map(fixture, fn(_k, v) { v * 2 }) == [Pair("a", 200), Pair("b", 400)]
/// ```
pub fn map(
self: Pairs<key, value>,
with: fn(key, value) -> result,
) -> Pairs<key, result> {
when self is {
[] -> []
[Pair(k, v), ..rest] -> [Pair(k, with(k, v)), ..map(rest, with)]
}
}

test map_1() {
let fixture = [Pair("a", 1), Pair("b", 2)]

map(fixture, with: fn(k, _) { k }) == [Pair("a", "a"), Pair("b", "b")]
}

test map_2() {
let fixture = [Pair("a", 1), Pair("b", 2)]

map(fixture, with: fn(_, v) { v + 1 }) == [Pair("a", 2), Pair("b", 3)]
}

/// Merge a value into the `Pairs` at a given key. If the key already exists,
/// its value is merged by the merging function.
/// > [!CAUTION]
/// > The list is only traversed up to the given key and the traversal
/// > stops as soon as a higher key is encountered. Said differently, the list
/// > is assumed to **be ordered by ascending keys**! If it is not, expect the
/// > unexpected.
///
/// ```aiken
/// use aiken/builtin
/// use aiken/primitive/bytearray
///
/// let compare_un_b_data = fn(l, r) {
/// bytearray.compare(l |> builtin.un_b_data, r |> builtin.un_b_data)
/// }
/// let add_integer = fn(x, y) { x + y }
///
/// let result =
/// []
/// |> pairs.merge_by_ascending_key(key: "foo" |> builtin.b_data, value: 1, compare: compare_un_b_data, merging: builtin.add_integer)
/// |> pairs.merge_by_ascending_key(key: "bar" |> builtin.b_data, value: 2, compare: compare_un_b_data, merging: builtin.add_integer)
/// |> pairs.merge_by_ascending_key(key: "foo" |> builtin.b_data, value: 3, compare: compare_un_b_data, merging: builtin.add_integer)
/// |> pairs.insert_with_by_ascending_key(key: "foo", value: 1, compare: bytearray.compare, with: add_integer)
/// |> pairs.insert_with_by_ascending_key(key: "bar", value: 2, compare: bytearray.compare, with: add_integer)
/// |> pairs.insert_with_by_ascending_key(key: "foo", value: 3, compare: bytearray.compare, with: add_integer)
///
/// result == [Pair("bar" |> builtin.b_data, 2), Pair("foo" |> builtin.b_data, 4)]
/// result == [Pair("bar", 2), Pair("foo", 4)]
/// ```
///
/// Notice that the key is of type `Data`. The function works with any key type, not just `ByteArray`,
/// so it's not the same as `dict.from_pairs() |> dict.union_with() |> dict.to_pairs()`
pub fn merge_by_ascending_key(
pub fn insert_with_by_ascending_key(
self: Pairs<key, value>,
key k: key,
value v: value,
compare: fn(key, key) -> Ordering,
merging: fn(value, value) -> value,
with: fn(value, value) -> value,
) -> Pairs<key, value> {
when self is {
[] ->
[Pair(k, v)]

[] -> [Pair(k, v)]
[Pair(k2, v2), ..rest] ->
when compare(k, k2) is {
Less ->
[Pair(k, v), ..self]

Equal ->
[Pair(k, merging(v, v2)), ..rest]

Greater ->
[Pair(k2, v2), ..merge_by_ascending_key(rest, k, v, compare, merging)]
if compare(k, k2) == Less {
[Pair(k, v), ..self]
} else {
if k == k2 {
[Pair(k, with(v, v2)), ..rest]
} else {
[
Pair(k2, v2),
..insert_with_by_ascending_key(rest, k, v, compare, with)
]
}
}
}
}

test merge_by_ascending_key_1() {
test insert_with_by_ascending_key_1() {
let compare_un_b_data =
fn(l, r) {
bytearray.compare(l |> builtin.un_b_data, r |> builtin.un_b_data)
}

let m =
[]
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"foo" |> builtin.b_data,
42,
compare_un_b_data,
builtin.add_integer,
)
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"foo" |> builtin.b_data,
14,
compare_un_b_data,
Expand All @@ -624,27 +601,27 @@ test merge_by_ascending_key_1() {
m == [Pair("foo" |> builtin.b_data, 56)]
}

test merge_by_ascending_key_2() {
test insert_with_by_ascending_key_2() {
let compare_un_b_data =
fn(l, r) {
bytearray.compare(l |> builtin.un_b_data, r |> builtin.un_b_data)
}

let m =
[]
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"foo" |> builtin.b_data,
42,
compare_un_b_data,
builtin.add_integer,
)
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"bar" |> builtin.b_data,
14,
compare_un_b_data,
builtin.add_integer,
)
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"baz" |> builtin.b_data,
1337,
compare_un_b_data,
Expand All @@ -658,27 +635,27 @@ test merge_by_ascending_key_2() {
]
}

test merge_by_ascending_key_3() {
test insert_with_by_ascending_key_3() {
let compare_un_b_data =
fn(l, r) {
bytearray.compare(l |> builtin.un_b_data, r |> builtin.un_b_data)
}

let result =
[]
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"foo" |> builtin.b_data,
1,
compare_un_b_data,
builtin.add_integer,
)
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"bar" |> builtin.b_data,
2,
compare_un_b_data,
builtin.add_integer,
)
|> merge_by_ascending_key(
|> insert_with_by_ascending_key(
"foo" |> builtin.b_data,
3,
compare_un_b_data,
Expand All @@ -688,9 +665,44 @@ test merge_by_ascending_key_3() {
result == [Pair("bar" |> builtin.b_data, 2), Pair("foo" |> builtin.b_data, 4)]
}

/// Apply a function to all key-value pairs in a alist, replacing the values.
///
/// ```aiken
/// let fixture = [Pair("a", 100), Pair("b", 200)]
///
/// pairs.map(fixture, fn(_k, v) { v * 2 }) == [Pair("a", 200), Pair("b", 400)]
/// ```
pub fn map(
self: Pairs<key, value>,
with: fn(key, value) -> result,
) -> Pairs<key, result> {
when self is {
[] -> []
[Pair(k, v), ..rest] -> [Pair(k, with(k, v)), ..map(rest, with)]
}
}

test map_1() {
let fixture = [Pair("a", 1), Pair("b", 2)]

map(fixture, with: fn(k, _) { k }) == [Pair("a", "a"), Pair("b", "b")]
}

test map_2() {
let fixture = [Pair("a", 1), Pair("b", 2)]

map(fixture, with: fn(_, v) { v + 1 }) == [Pair("a", 2), Pair("b", 3)]
}

/// Insert a value in the `Pairs` at a given key. If the key already exists,
/// its value is replaced.
///
/// > [!CAUTION]
/// > The list is only traversed up to the given key and the traversal
/// > stops as soon as a higher key is encountered. Said differently, the list
/// > is assumed to **be ordered by ascending keys**! If it is not, expect the
/// > unexpected.
///
/// ```aiken
/// use aiken/primitive/bytearray
///
Expand Down

0 comments on commit 2b54b43

Please sign in to comment.