Packages

Ordered Dict implementation in gleam

Current section

Files

Jump to
ordered_dict src ordered_dict.gleam
Raw

src/ordered_dict.gleam

import gleam/dict
import gleam/int
import gleam/list
import gleam/order
import gleam/result
// TODO: Duplication strategy
// pub type DuplicateStrategy {
// NewIndex
// OldIndex
// KeepBoth
// }
pub opaque type OrderedDict(k, v) {
OrderedDict(map: dict.Dict(k, v), order: List(k))
}
pub fn new() -> OrderedDict(k, v) {
OrderedDict(dict.new(), [])
}
/// Converts a list of 2-element tuples `#(key, value)` to an ordered dict.
///
/// If two tuples have the same key the last one in the list will be the one
/// that is present in the dict, and .
///
pub fn from_list(entries: List(#(k, v))) -> OrderedDict(k, v) {
let #(d, rev) = {
use #(map, rev_order), #(key, val) <- list.fold(entries, #(dict.new(), []))
#(dict.insert(map, key, val), [key, ..rev_order])
}
OrderedDict(d, list.reverse(rev))
}
/// Determines whether or not a value present in the dict for a given key.
///
/// ## Examples
///
/// ```gleam
/// new() |> insert_end("a", 0) |> has_key("a")
/// // -> True
/// ```
///
/// ```gleam
/// new() |> insert_end("a", 0) |> has_key("b")
/// // -> False
/// ```
///
pub fn has_key(o_dict: OrderedDict(k, v), key: k) -> Bool {
dict.has_key(o_dict.map, key)
}
/// 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_end("key", "value") |> size
/// // -> 1
/// ```
///
pub fn size(o_dict: OrderedDict(k, v)) -> Int {
list.length(o_dict.order)
}
/// Determines whether or not the dict is empty.
///
/// ## Examples
///
/// ```gleam
/// new() |> is_empty
/// // -> True
/// ```
///
/// ```gleam
/// new() |> insert_end("b", 1) |> is_empty
/// // -> False
/// ```
///
pub fn is_empty(o_dict: OrderedDict(k, v)) -> Bool {
dict.is_empty(o_dict.map)
}
/// Inserts a value into the dict with the given key as the first item.
///
/// If the dict already has a value for the given key then the value is replaced with the new value.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> prepend("c", 2)
/// // -> from_list([#("c", 2), #("a", 0), #("b", 1)])
/// ```
///
pub fn prepend(
into o_dict: OrderedDict(k, v),
for key: k,
value val: v,
) -> OrderedDict(k, v) {
OrderedDict(dict.insert(o_dict.map, key, val), [key, ..o_dict.order])
}
/// Inserts a value into the dict with the given key as the last item.
///
/// If the dict already has a value for the given key then the value is replaced with the new value.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> insert_end("c", 2)
/// // -> from_list([#("a", 0), #("b", 1), #("c", 2)])
/// ```
///
pub fn insert_end(
into o_dict: OrderedDict(k, v),
for key: k,
value val: v,
) -> OrderedDict(k, v) {
OrderedDict(
dict.insert(o_dict.map, key, val),
list.append(o_dict.order, [key]),
)
}
/// Inserts a value into the dict with the given key and position.
///
/// If the dict already has a value for the given key then the value is replaced with the new value.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> insert(1, "c", 2)
/// // -> from_list([#("a", 0), #("c", 2), #("b", 1)])
/// ```
///
pub fn insert(
into o_dict: OrderedDict(k, v),
at index: Int,
for key: k,
value val: v,
) -> OrderedDict(k, v) {
OrderedDict(dict.insert(o_dict.map, key, val), case
list.split(o_dict.order, index)
{
#(pre, []) -> list.append(pre, [key])
#(pre, post) -> list.append(pre, [key, ..post])
})
}
/// Fetches a key from a dict at a given index.
///
/// The dict may not have a key for the index, so the key is wrapped in a
/// `Result`.
///
/// ## Examples
///
/// ```gleam
/// new() |> insert("a", 0) |> get_key_at(0)
/// // -> Ok("a")
/// ```
///
/// ```gleam
/// new() |> insert("a", 0) |> get_key_at(1)
/// // -> Error(Nil)
/// ```
///
pub fn get_key_at(
from o_dict: OrderedDict(k, v),
at index: Int,
) -> Result(k, Nil) {
case list.split(o_dict.order, index) {
#(_, []) -> Error(Nil)
#(_, [key, ..]) -> Ok(key)
}
}
/// Fetches a value from a dict at a given index.
///
/// The dict may not have a value for the index, so the value is wrapped in a
/// `Result`.
///
/// ## Examples
///
/// ```gleam
/// new() |> insert("a", 0) |> get_value_at(0)
/// // -> Ok(0)
/// ```
///
/// ```gleam
/// new() |> insert("a", 0) |> get_value_at(1)
/// // -> Error(Nil)
/// ```
///
pub fn get_value_at(
from o_dict: OrderedDict(k, v),
at index: Int,
) -> Result(v, Nil) {
case list.split(o_dict.order, index) {
#(_, []) -> Error(Nil)
#(_, [key, ..]) -> dict.get(o_dict.map, key)
}
}
/// Fetches a `#(key, value)` entry from a dict at a given index.
///
/// The dict may not have an entry for the index, so the entry is wrapped in a
/// `Result`.
///
/// ## Examples
///
/// ```gleam
/// new() |> insert("a", 0) |> get_at(0)
/// // -> Ok(#("a",0))
/// ```
///
/// ```gleam
/// new() |> insert("a", 0) |> get_at(1)
/// // -> Error(Nil)
/// ```
///
pub fn get_at(
from o_dict: OrderedDict(k, v),
at index: Int,
) -> Result(#(k, v), Nil) {
case list.split(o_dict.order, index) {
#(_, []) -> Error(Nil)
#(_, [key, ..]) ->
dict.get(o_dict.map, key) |> result.map(fn(val) { #(key, val) })
}
}
/// Fetches a value from a dict for a given key.
///
/// The dict 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 o_dict: OrderedDict(k, v), for key: k) -> Result(v, Nil) {
dict.get(o_dict.map, key)
}
/// Fetches an index from a dict for a given key.
///
/// The dict may not have an index for the key, so the index is wrapped in a
/// `Result`.
///
/// ## Examples
///
/// ```gleam
/// new() |> insert("a", "foo") |> get("a")
/// // -> Ok(0)
/// ```
///
/// ```gleam
/// new() |> insert("a", "foo") |> get("b")
/// // -> Error(Nil)
/// ```
///
pub fn get_index(from o_dict: OrderedDict(k, v), for key: k) -> Result(Int, Nil) {
case has_key(o_dict, key) {
True -> {
let len = list.length(o_dict.order)
let idx = {
use i, k <- list.fold_until(o_dict.order, 0)
case k == key {
True -> list.Stop(i)
_ -> list.Continue(i + 1)
}
}
case idx < len {
True -> Ok(idx)
_ -> Error(Nil)
}
}
_ -> Error(Nil)
}
}
/// Creates a new dict from a given dict with all the same entries except for the
/// one at a given index, if it exists and it is the specified key.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete_at_with_key(0, "a")
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete_at_with_key(0, "b")
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete_at_with_key(1, "a")
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete_at_with_key(2, "a")
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
pub fn delete_at_with_key(
from o_dict: OrderedDict(k, v),
at index: Int,
for key: k,
) -> OrderedDict(k, v) {
case list.split(o_dict.order, index) {
#(pre, [key_at, ..post]) if key == key_at ->
OrderedDict(dict.delete(o_dict.map, key_at), list.append(pre, post))
_ -> o_dict
}
}
/// Creates a new dict from a given dict with all the same entries except for the
/// one at a given index, if it exists.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete_at(0)
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete_at(2)
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
pub fn delete_at(
from o_dict: OrderedDict(k, v),
at index: Int,
) -> OrderedDict(k, v) {
case list.split(o_dict.order, index) {
#(pre, [key_at, ..post]) ->
OrderedDict(dict.delete(o_dict.map, key_at), list.append(pre, post))
_ -> o_dict
}
}
/// Creates a new dict from a given dict with all the same entries except for the
/// one with a given key, if it exists.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete("a")
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete("c")
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
pub fn delete(
from o_dict: OrderedDict(k, v),
delete key: k,
) -> OrderedDict(k, v) {
OrderedDict(
dict.delete(o_dict.map, key),
list.filter(o_dict.order, fn(item) { item != key }),
)
}
/// Creates a new dict from a given dict with all the same entries except any with
/// keys found in a given list.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> drop(["a"])
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> drop(["c"])
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> drop(["a", "b", "c"])
/// // -> from_list([])
/// ```
///
pub fn drop(from o_dict: OrderedDict(k, v), drop disallowed_keys: List(k)) {
OrderedDict(
dict.drop(o_dict.map, disallowed_keys),
list.filter(o_dict.order, fn(item) { !list.contains(disallowed_keys, item) }),
)
}
/// Creates a new ordered dict from a given ordered dict, 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 o_dict: OrderedDict(k, v),
keeping desired_keys: List(k),
) -> OrderedDict(k, v) {
OrderedDict(
dict.take(o_dict.map, desired_keys),
list.filter(o_dict.order, list.contains(desired_keys, _)),
)
}
/// Creates a new ordered dict from a given ordered dict, minus any entries that a
/// given function returns `False` for.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)])
/// |> filter(fn(key, value, _index) { value != 0 })
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)])
/// |> filter(fn(key, value, _index) { True })
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
pub fn filter(
in o_dict: OrderedDict(k, v),
keeping predicate: fn(k, v, Int) -> Bool,
) -> OrderedDict(k, v) {
to_list_indexed(o_dict)
|> list.filter_map(fn(e) {
case predicate(e.1, e.2, e.0) {
True -> Ok(#(e.1, e.2))
_ -> Error(Nil)
}
})
|> from_list()
}
/// Change the order of a specific item, shifting it from one index to another.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1), #("c", 2)]) |> reorder(2, 0)
/// // -> from_list([#("c", 2), #("a", 0), #("b", 1)])
/// ```
///
/// The item will be removed and readded simultaneously, so the indices of all
/// the items between old and new indices will shift by one.
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3), #("e", 4), #("f", 5)]) |> reorder(1, 3)
/// // -> from_list([#("a", 0), #("c", 2), #("d", 3), #("b", 1), #("e", 4), #("f", 5)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3), #("e", 4), #("f", 5)]) |> reorder(4, 2)
/// // -> from_list([#("a", 0), #("b", 1), #("e", 4), #("c", 2), #("d", 3), #("f", 5)])
/// ```
///
pub fn reorder(
in o_dict: OrderedDict(k, v),
from old_index: Int,
to new_index: Int,
) -> OrderedDict(k, v) {
let old = int.min(old_index, int.max(0, list.length(o_dict.order) - 1))
let delta = int.absolute_value(new_index - old)
OrderedDict(
..o_dict,
order: case int.compare(new_index, old) {
order.Eq -> o_dict.order
order.Gt -> {
let assert #(pre, [key, ..rest]) = list.split(o_dict.order, old)
let #(mid, post) = list.split(rest, delta)
list.flatten([pre, mid, [key], post])
}
order.Lt -> {
let assert #(pre, [key, ..rest]) = list.split(o_dict.order, new_index)
let #(mid, post) = list.split(rest, delta)
list.flatten([pre, [key], mid, post])
}
},
)
}
/// Converts the dict to an ordered list of 2-element tuples `#(key, value)`,
/// one for each key-value pair in the dict.
///
/// ## Examples
///
/// Calling `to_list` on an empty `dict` returns an empty list.
///
/// ```gleam
/// new() |> to_list
/// // -> []
/// ```
///
/// The tuples will be ordered as specified
///
/// ```gleam
/// new() |> insert_end("b", 1) |> insert_end("a", 0) |> insert_at(0, "c", 2) |> to_list
/// // -> [#("c", 2), #("b", 1), #("a", 0)]
/// ```
///
pub fn to_list(o_dict: OrderedDict(k, v)) -> List(#(k, v)) {
use key <- list.filter_map(o_dict.order)
dict.get(o_dict.map, key)
|> result.map(fn(d) { #(key, d) })
}
/// Converts the dict to an ordered list of 2-element tuples `#(key, value)`,
/// one for each key-value pair in the dict.
///
/// ## Examples
///
/// Calling `to_list` on an empty `dict` returns an empty list.
///
/// ```gleam
/// new() |> to_list
/// // -> []
/// ```
///
/// The tuples will be ordered as specified
///
/// ```gleam
/// new() |> insert_end("b", 1) |> insert_end("a", 0) |> insert_at(0, "c", 2) |> to_list
/// // -> [#("c", 2), #("b", 1), #("a", 0)]
/// ```
///
pub fn to_list_indexed(o_dict: OrderedDict(k, v)) -> List(#(Int, k, v)) {
{
use #(out, i), key <- list.fold(o_dict.order, #([], 0))
case dict.get(o_dict.map, key) {
Ok(val) -> #([#(i, key, val), ..out], i + 1)
_ -> #(out, i)
}
}.0
}
/// Gets a list of all keys in a given ordered dict in order.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> keys
/// // -> ["a", "b"]
/// ```
///
pub fn keys(o_dict: OrderedDict(k, v)) -> List(k) {
list.filter(o_dict.order, dict.has_key(o_dict.map, _))
}
/// Get the internal unordered dict
pub fn backing_dict(o_dict: OrderedDict(k, v)) -> dict.Dict(k, v) {
o_dict.map
}
/// Combines all entries into a single value by calling a given function on each
/// one in order.
///
/// # Examples
///
/// ```gleam
/// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)])
/// fold(dict, 0, fn(accumulator, key, value, _) { accumulator + value })
/// // -> 13
/// ```
///
/// ```gleam
/// import gleam/string
///
/// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)])
/// fold(dict, "", fn(accumulator, key, value, _) {
/// string.append(accumulator, key)
/// })
/// // -> "abc"
/// ```
///
pub fn fold(
over o_dict: OrderedDict(k, v),
from initial: acc,
with fun: fn(acc, k, v, Int) -> acc,
) -> acc {
use acc, #(idx, key, val) <- list.fold(to_list_indexed(o_dict), initial)
fun(acc, key, val, idx)
}
/// Calls a function for each key and value in order, 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(k, v) {
/// io.println(key <> " => " <> value)
/// })
/// // -> Nil
/// // a => apple
/// // b => banana
/// // c => cherry
/// ```
///
/// The order of elements in the iteration is an implementation detail that
/// should not be relied upon.
///
pub fn each(o_dict: OrderedDict(k, v), fun: fn(k, v, Int) -> a) -> Nil {
fold(o_dict, Nil, fn(nil, k, v, i) {
fun(k, v, i)
nil
})
}
/// Updates all values in a given dict by calling a given function on each key,
/// value and index.
///
/// ## Examples
///
/// ```gleam
/// from_list([#(3, 3), #(2, 4)])
/// |> map_values(fn(key, value, _index) { key * value })
/// // -> from_list([#(3, 9), #(2, 8)])
/// ```
///
pub fn map_values(
in o_dict: OrderedDict(k, v),
with fun: fn(k, v, Int) -> a,
) -> OrderedDict(k, a) {
to_list_indexed(o_dict)
|> list.map(fn(e) { #(e.1, fun(e.1, e.2, e.0)) })
|> from_list()
}
pub type UpsertIndex {
Start
End
Index(Int)
}
pub type Upsert(k, v) {
Update(value: v, update: fn(v) -> OrderedDict(k, v))
Insert(insert: fn(UpsertIndex, v) -> OrderedDict(k, v))
}
/// Creates a new dict with one entry inserted at the end or updated using a given function.
///
/// If there was not an entry in the dict for the given key then the function
/// gets `Insert` as its argument, otherwise it gets `Update(value)`.
///
/// ## Example
///
/// ```gleam
/// let dict = from_list([#("a", 0)])
/// let increment = fn(x) {
/// case x {
/// Update(i, update) -> update(i + 1)
/// Insert(insert) -> insert(End, 0)
/// }
/// }
///
/// upsert(dict, "a", increment)
/// // -> from_list([#("a", 1)])
///
/// upsert(dict, "b", increment)
/// // -> from_list([#("a", 0), #("b", 0)])
/// ```
///
pub fn upsert(
in o_dict: OrderedDict(k, v),
update key: k,
with fun: fn(Upsert(k, v)) -> OrderedDict(k, v),
) -> OrderedDict(k, v) {
case get(o_dict, key) {
Ok(val) ->
Update(val, fn(val) {
OrderedDict(..o_dict, map: dict.insert(o_dict.map, key, val))
})
_ ->
Insert(fn(index, val) {
case index {
Start -> prepend(o_dict, key, val)
Index(i) if i <= 0 -> prepend(o_dict, key, val)
Index(i) -> insert(o_dict, i, key, val)
End -> insert_end(o_dict, key, val)
}
})
}
|> fun
}