aiken/collection/dict

A module for working with bytearray dictionaries.

Dictionaries are ordered sets of key-value pairs, which thus preserve some invariants. Specifically, each key is only present once in the dictionary and all keys are stored in ascending lexicographic order.

These invariants allow for more optimized functions to operate on Dict, but as a trade-offs, prevent Dict from being serializable. To recover a Dict from an unknown Data, you must first recover an Pairs<k, v> and use dict.from_ascending_list.

Types

An opaque Dict. The type is opaque because the module maintains some invariant, namely: there’s only one occurrence of a given key in the dictionary.

Note that the key parameter is a phantom-type, and only present as a means of documentation. Keys can be any type, yet will need to comparable to use functions like insert.

See for example:

pub type Value =
  Dict<PolicyId, Dict<AssetName, Int>>

A function callback to accumulate two returned values at once. See also foldl2 and foldr2 about usage.

Alias

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

Constants

empty: Dict<a, b>

An empty dictionary.

dict.to_pairs(dict.empty) == []

Functions

Constructing

from_ascending_pairs(xs: Pairs<ByteArray, value>) -> Dict<key, value>

Like ‘from_pairs’, but from an already sorted list by ascending keys. This function fails (i.e. halts the program execution) if the list isn’t sorted.

let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)]

let result =
  dict.from_ascending_pairs(pairs)
    |> dict.to_pairs()

result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]

This is meant to be used to turn a list constructed off-chain into a Dict which has taken care of maintaining interval invariants. This function still performs a sanity check on all keys to avoid silly mistakes. It is, however, considerably faster than ‘from_pairs’

from_ascending_pairs_with(
  xs: Pairs<ByteArray, value>,
  predicate: fn(value) -> Bool,
) -> Dict<key, value>

Like from_ascending_pairs but fails if any value doesn’t satisfy the predicate.

let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)]

dict.from_ascending_pairs_with(pairs, fn(x) { x <= 250 }) // fail

from_pairs(self: Pairs<ByteArray, value>) -> Dict<key, value>

Construct a dictionary from a list of key-value pairs. Note that when a key is present multiple times, the first occurrence prevails.

let pairs = [Pair("a", 100), Pair("c", 300), Pair("b", 200)]

let result =
  dict.from_pairs(pairs)
    |> dict.to_pairs()

result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]

singleton(key: ByteArray, value: value) -> Dict<key, value>

Construct a dictionary from a single key/value pair

Inspecting Forcibly

expect_contains(
  self: Dict<key, value>,
  subset: Dict<key, value>,
  compare: fn(value, value) -> Ordering,
) -> Void

More efficient version of contains, which fails if the given dict isn’t fully contained in the source one.

expect_find(self: Dict<key, value>, value: value) -> ByteArray

More efficient version of find, which fails if the value isn’t found in the dict.

expect_get(self: Dict<key, value>, key: ByteArray) -> value

More efficient version of get, which fails if the key isn’t found in the dict.

expect_has_key(self: Dict<key, value>, key: ByteArray) -> Void

More efficient version of has_key, which fails if the key isn’t present in the dict

Inspecting

contains(
  self: Dict<key, value>,
  subset: Dict<key, value>,
  compare: fn(value, value) -> Ordering,
) -> Bool

Check whether a dictionary is fully “contained” within another using the comparison function. A dict is contained in another if:

  1. All its keys also exist in the first dict
  2. Each value is at least as large according to the comparison function
let superset = dict.empty
  |> dict.insert("a", 10)
  |> dict.insert("b", 42)

dict.contains(superset, dict.empty, int.compare)
dict.contains(superset, dict.from_pairs([Pair("a", 1)]), int.compare)
dict.contains(superset, dict.from_pairs([Pair("a", 1), Pair("b", 42)]), int.compare)
!dict.contains(superset, dict.from_pairs([Pair("a", 14)]), int.compare)
!dict.contains(superset, dict.from_pairs([Pair("a", 1), Pair("c", 1)]), int.compare)

find(self: Dict<key, value>, value: value) -> Option<ByteArray>

Finds a value in the dictionary, and returns the first key found to have that value.

let result =
  dict.empty
    |> dict.insert(key: "a", value: 42)
    |> dict.insert(key: "b", value: 14)
    |> dict.insert(key: "c", value: 42)
    |> dict.find(42)

result == Some("a")

get(self: Dict<key, value>, key: ByteArray) -> Option<value>

Get a value in the dict by its key.

let result =
  dict.empty
    |> dict.insert(key: "a", value: "Aiken")
    |> dict.get(key: "a")

 result == Some("Aiken")

get_or_else(
  self: Dict<key, value>,
  key: ByteArray,
  or_else: fn() -> value,
) -> value

Get a value from the dict by its key, or fallback to a default one.

let kvs =
  dict.empty
    |> dict.insert(key: "a", value: 42)
    |> dict.insert(key: "b", value: 14)

dict.get_or_else(kvs, "a", fn() { 0 }) == 42
dict.get_or_else(kvs, "b", fn() { 0 }) == 14
dict.get_or_else(kvs, "c", fn() { 0 }) == 0

has_key(self: Dict<key, value>, key: ByteArray) -> Bool

Check if a key exists in the dictionary.

let result =
  dict.empty
    |> dict.insert(key: "a", value: "Aiken")
    |> dict.has_key("a")

result == True

is_empty(self: Dict<key, value>) -> Bool

Efficiently checks whether a dictionary is empty.

dict.is_empty(dict.empty) == True

keys(self: Dict<key, value>) -> List<ByteArray>

Extract all the keys present in a given Dict.

let result =
  dict.empty
    |> dict.insert("a", 14)
    |> dict.insert("b", 42)
    |> dict.insert("a", 1337)
    |> dict.keys()

result == ["a", "b"]

size(self: Dict<key, value>) -> Int

Return the number of key-value pairs in the dictionary.

let result =
  dict.empty
    |> dict.insert("a", 100)
    |> dict.insert("b", 200)
    |> dict.insert("c", 300)
    |> dict.size()

result == 3

values(self: Dict<key, value>) -> List<value>

Extract all the values present in a given Dict.

let result =
  dict.empty
    |> dict.insert("a", 14)
    |> dict.insert("b", 42)
    |> dict.insert("c", 1337)
    |> dict.values()

result == [14, 42, 1337]

Modifying

delete(self: Dict<key, value>, key: ByteArray) -> Dict<key, value>

Remove a key-value pair from the dictionary. If the key is not found, no changes are made.

let result =
  dict.empty
    |> dict.insert(key: "a", value: 100)
    |> dict.insert(key: "b", value: 200)
    |> dict.delete(key: "a")
    |> dict.to_pairs()

result == [Pair("b", 200)]

difference_with(
  left: Dict<key, value>,
  right: Dict<key, value>,
  with_both: UnionStrategy<ByteArray, value>,
  with_right: InsertStrategy<ByteArray, value>,
) -> Dict<key, value>

Like union_with but allows specifying the behavior to adopt when a key is present only in the right dictionary. The first value received corresponds to the value in the right dictionary. When passing None, the value is removed and not present in the union.

let left_dict = dict.from_pairs([Pair("a", 100), Pair("b", 200)])
let right_dict = dict.from_pairs([Pair("a", 150), Pair("c", 300)])

let result =
  dict.difference_with(
    left_dict,
    right_dict,
    fn(_k, _v_left, _v_right, _keep, discard) { discard() },
    fn(_k, v_right, keep, _discard) { keep(v_right) },
  )
    |> dict.to_pairs()

result == [Pair("b", 200)]

filter(
  self: Dict<key, value>,
  with: fn(ByteArray, value) -> Bool,
) -> Dict<key, value>

Keep only the key-value pairs that pass the given predicate.

let result =
  dict.empty
    |> dict.insert(key: "a", value: 100)
    |> dict.insert(key: "b", value: 200)
    |> dict.insert(key: "c", value: 300)
    |> dict.filter(fn(k, _v) { k != "a" })
    |> dict.to_pairs()

result == [Pair("b", 200), Pair("c", 300)]

insert(self: Dict<key, value>, key: ByteArray, value: value) -> Dict<key, value>

Insert a value in the dictionary at a given key. If the key already exists, its value is overridden. If you need ways to combine keys together, use insert_with.

let result =
  dict.empty
    |> dict.insert(key: "a", value: 1)
    |> dict.insert(key: "b", value: 2)
    |> dict.insert(key: "a", value: 3)
    |> dict.to_pairs()

result == [Pair("a", 3), Pair("b", 2)]

insert_with(
  self: Dict<key, value>,
  key: ByteArray,
  value: value,
  with: UnionStrategy<ByteArray, value>,
) -> Dict<key, value>

Insert a value in the dictionary at a given key. When the key already exists, the provided 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.

use aiken/collection/dict/strategy

let result =
  dict.empty
    |> dict.insert_with(key: "a", value: 1, with: strategy.sum())
    |> dict.insert_with(key: "b", value: 2, with: strategy.sum())
    |> dict.insert_with(key: "a", value: 3, with: strategy.sum())
    |> dict.to_pairs()

result == [Pair("a", 4), Pair("b", 2)]

map(self: Dict<key, a>, with: fn(ByteArray, a) -> b) -> Dict<key, b>

Apply a function to all key-value pairs in a Dict.

let result =
  dict.empty
    |> dict.insert("a", 100)
    |> dict.insert("b", 200)
    |> dict.insert("c", 300)
    |> dict.map(fn(_k, v) { v * 2 })
    |> dict.to_pairs()

 result == [Pair("a", 200), Pair("b", 400), Pair("c", 600)]

pop(self: Dict<key, value>, key: ByteArray) -> (Option<value>, Dict<key, value>)

Remove a key-value pair from the dictionary and return its value. If the key is not found, no changes are made.

let (value, _) =
  dict.empty
    |> dict.insert(key: "a", value: 100)
    |> dict.insert(key: "b", value: 200)
    |> dict.pop(key: "a")

value == 100

Modifying Forcibly

expect_delete(self: Dict<key, value>, key: ByteArray) -> Dict<key, value>

More efficient version of delete, which fails if the key isn’t present in the dict.

expect_pop(self: Dict<key, value>, key: ByteArray) -> (value, Dict<key, value>)

More efficient version of pop, which fails if the key isn’t present in the dict.

expect_tail(self: Dict<key, value>) -> Dict<key, value>

Drop the first key/value out of a dictionnary, and fails if the dictionnary is empty.

dict.expect_tail(dict.empty) → 💥
dict.expect_tail(dict.singleton("a", 1)) == dict.empty
dict.expect_tail(dict.from_pairs([Pair("a", 1), Pair("b", 2)])) == dict.singleton("b", 2)

Combining

union(left: Dict<key, value>, right: Dict<key, value>) -> Dict<key, value>

Combine two dictionaries. If the same key exist in both the left and right dictionary, values from the left are preferred (i.e. left-biaised).

let left_dict = dict.from_pairs([Pair("a", 100), Pair("b", 200)])
let right_dict = dict.from_pairs([Pair("a", 150), Pair("c", 300)])

let result =
  dict.union(left_dict, right_dict) |> dict.to_pairs()

result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]

union_with(
  left: Dict<key, value>,
  right: Dict<key, value>,
  with: UnionStrategy<ByteArray, value>,
) -> Dict<key, value>

Like union but allows specifying the behavior to adopt when a key is present in both dictionaries. The first value received corresponds to the value in the left dictionary, whereas the second argument corresponds to the value in the right dictionary.

When passing None, the value is removed and not present in the union.

let left_dict = dict.from_pairs([Pair("a", 100), Pair("b", 200)])
let right_dict = dict.from_pairs([Pair("a", 150), Pair("c", 300)])

let result =
  dict.union_with(
    left_dict,
    right_dict,
    fn(_k, v1, v2) { Some(v1 + v2) },
  )
    |> dict.to_pairs()

result == [Pair("a", 250), Pair("b", 200), Pair("c", 300)]

Transforming

foldl(
  self: Dict<key, value>,
  zero: result,
  with: fn(ByteArray, value, result) -> result,
) -> result

Fold over the key-value pairs in a dictionary. The fold direction follows keys in ascending order and is done from left-to-right.

let result =
  dict.empty
    |> dict.insert(key: "a", value: 100)
    |> dict.insert(key: "b", value: 200)
    |> dict.insert(key: "c", value: 300)
    |> dict.foldl(0, fn(_k, v, r) { v + r })

result == 600

foldl2(
  self: Dict<key, value>,
  zero_a: a,
  zero_b: b,
  with: fn(ByteArray, value, a, b, Fold2<a, b, result>) -> result,
  return: Fold2<a, b, result>,
) -> result

foldr(
  self: Dict<key, value>,
  zero: result,
  with: fn(ByteArray, value, result) -> result,
) -> result

Fold over the key-value pairs in a dictionary. The fold direction follows keys in ascending order and is done from right-to-left.

let result =
  dict.empty
    |> dict.insert(key: "a", value: 100)
    |> dict.insert(key: "b", value: 200)
    |> dict.insert(key: "c", value: 300)
    |> dict.foldr(0, fn(_k, v, r) { v + r })

result == 600

foldr2(
  self: Dict<key, value>,
  zero_a: a,
  zero_b: b,
  with: fn(ByteArray, value, a, b, Fold2<a, b, result>) -> result,
  return: Fold2<a, b, result>,
) -> result

to_pairs(self: Dict<key, value>) -> Pairs<ByteArray, value>

Get the inner list holding the dictionary data.

let result =
  dict.empty
    |> dict.insert("a", 100)
    |> dict.insert("b", 200)
    |> dict.insert("c", 300)
    |> dict.to_pairs()

result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
Search Document