Skip to content

Commit

Permalink
docs docs docsgit add .
Browse files Browse the repository at this point in the history
  • Loading branch information
schurhammer committed May 7, 2024
1 parent 4150bf8 commit 8344aec
Show file tree
Hide file tree
Showing 14 changed files with 377 additions and 157 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Data structures in pure Gleam.

### Priority Queue


`gleamy/priority_queue`:

This priority queue is a wrapper around `gleamy/pairing_heap` ,providing additional functionality. The priority is comparison based in ascending order (lowest priority first).
This priority queue is a wrapper around `gleamy/pairing_heap`, providing additional functionality. The priority is comparison based in ascending order (lowest value first).

You may use the pairing heap or other heap structures directly for much of the same functionality.

### Heap

Expand Down
68 changes: 46 additions & 22 deletions src/gleamy/leftist_heap.gleam
Original file line number Diff line number Diff line change
@@ -1,61 +1,85 @@
//// This module provides an implementation of the leftist heap data structure,
//// a type of binary heap with efficient insert, find_min, and delete_min, and merge operations.

// Based on "Purely Functional Data Structures" by Okasaki (1998)

import gleam/order.{type Order, Gt}

type T(a) {
E
T(Int, a, T(a), T(a))
type Tree(a) {
Empty
Tree(Int, a, Tree(a), Tree(a))
}

pub opaque type Heap(a) {
Heap(root: T(a), compare: fn(a, a) -> Order)
Heap(root: Tree(a), compare: fn(a, a) -> Order)
}

/// Creates a new empty heap with the provided comparison function.
pub fn new(compare: fn(a, a) -> Order) -> Heap(a) {
Heap(E, compare)
Heap(Empty, compare)
}

/// Inserts a new item into the heap, preserving the heap property.
/// Time complexity: O(log n)
pub fn insert(heap: Heap(a), item: a) -> Heap(a) {
Heap(merge(T(1, item, E, E), heap.root, heap.compare), heap.compare)
Heap(
merge_trees(Tree(1, item, Empty, Empty), heap.root, heap.compare),
heap.compare,
)
}

/// Returns the minimum element in the heap, if the heap is not empty.
/// Time complexity: O(1)
pub fn find_min(heap: Heap(a)) -> Result(a, Nil) {
case heap.root {
T(_, x, _, _) -> Ok(x)
E -> Error(Nil)
Tree(_, x, _, _) -> Ok(x)
Empty -> Error(Nil)
}
}

/// Removes and returns the minimum element from the heap, along with the
/// new heap after deletion, if the heap is not empty.
/// Time complexity: O(log n)
pub fn delete_min(heap: Heap(a)) -> Result(#(a, Heap(a)), Nil) {
case heap.root {
T(_, x, a, b) -> Ok(#(x, Heap(merge(a, b, heap.compare), heap.compare)))
E -> Error(Nil)
Tree(_, x, a, b) ->
Ok(#(x, Heap(merge_trees(a, b, heap.compare), heap.compare)))
Empty -> Error(Nil)
}
}

fn merge(h1: T(a), h2: T(a), compare: fn(a, a) -> Order) -> T(a) {
/// Merges two heaps into a new heap containing all elements from both heaps,
/// preserving the heap property.
/// The given heaps must have the same comparison function.
/// Time complexity: O(log n)
pub fn merge(heap1: Heap(a), heap2: Heap(a)) -> Heap(a) {
let compare = heap1.compare
Heap(merge_trees(heap1.root, heap2.root, compare), compare)
}

fn merge_trees(h1: Tree(a), h2: Tree(a), compare: fn(a, a) -> Order) -> Tree(a) {
case h1, h2 {
h, E -> h
E, h -> h
T(_, x, a1, b1), T(_, y, a2, b2) ->
h, Empty -> h
Empty, h -> h
Tree(_, x, a1, b1), Tree(_, y, a2, b2) ->
case compare(x, y) {
Gt -> make(y, a2, merge(h1, b2, compare))
_ -> make(x, a1, merge(b1, h2, compare))
Gt -> make(y, a2, merge_trees(h1, b2, compare))
_ -> make(x, a1, merge_trees(b1, h2, compare))
}
}
}

fn make(x, a, b) {
let rank_a = case a {
T(r, _, _, _) -> r
E -> 0
Tree(r, _, _, _) -> r
Empty -> 0
}
let rank_b = case b {
T(r, _, _, _) -> r
E -> 0
Tree(r, _, _, _) -> r
Empty -> 0
}
case rank_a < rank_b {
True -> T(rank_a + 1, x, b, a)
_ -> T(rank_b + 1, x, a, b)
True -> Tree(rank_a + 1, x, b, a)
_ -> Tree(rank_b + 1, x, a, b)
}
}
36 changes: 30 additions & 6 deletions src/gleamy/map.gleam
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
//// This module provides an implementation of an ordered map data structure based on
//// red-black trees. It associates keys of type `k` with values of type `v`.
//// Keys are ordered by the comparison function.

import gleam/list
import gleam/order.{type Order}
import gleamy/red_black_tree_map as tree

/// The `Map(k, v)` type represents a map that associates keys of type `k`
/// with values of type `v`.
pub type Map(k, v) =
tree.Tree(k, v)
tree.Map(k, v)

/// Creates a new empty map with the provided comparison function for keys.
pub fn new(compare: fn(k, k) -> Order) -> Map(k, v) {
tree.new(compare)
}

/// Inserts a new key-value pair into the map, overwriting the value if the key
/// already exists.
pub fn insert(into map: Map(k, v), key key: k, value value: v) -> Map(k, v) {
tree.insert(map, key, value)
}

pub fn find(in map: Map(k, v), key key: k) -> Result(#(k, v), Nil) {
/// Get the value associated with a given key in the map, if present.
pub fn get(in map: Map(k, v), key key: k) -> Result(v, Nil) {
tree.find(map, key)
}

/// Checks if the map contains a given key.
pub fn has_key(in map: Map(k, v), key key: k) -> Bool {
case tree.find(map, key) {
Ok(_) -> True
Error(_) -> False
}
}

/// Removes a key-value pair from the map, if the key exists.
pub fn delete(from map: Map(k, v), this key: k) -> Map(k, v) {
tree.delete(map, key)
}

/// Returns the number of key-value pairs in the map.
/// Time complexity: O(n)
pub fn count(map: Map(k, v)) -> Int {
tree.fold(map, 0, fn(a, _, _) { a + 1 })
}

/// Applies a function to every key-value pair in the map, accumulating
/// the results with the provided initial accumulator value.
pub fn fold(
over map: Map(k, v),
from initial: a,
Expand All @@ -40,6 +56,8 @@ pub fn fold(
tree.fold(map, initial, reducer)
}

/// Creates a new map containing only the key-value pairs from the original map
/// that satisfy a given predicate function.
pub fn filter(in map: Map(k, v), for property: fn(k, v) -> Bool) -> Map(k, v) {
tree.fold(map, map, fn(map, k, v) {
case property(k, v) {
Expand All @@ -49,22 +67,27 @@ pub fn filter(in map: Map(k, v), for property: fn(k, v) -> Bool) -> Map(k, v) {
})
}

pub fn merge(this first: Map(k, v), and second: Map(k, v)) -> Map(k, v) {
tree.fold(first, second, fn(a, k, v) { tree.insert(a, k, v) })
/// Merges two maps into a new map, keeping values from the second map
/// if keys collide.
pub fn merge(intro dict: Map(k, v), from new_entries: Map(k, v)) -> Map(k, v) {
tree.fold(new_entries, dict, fn(a, k, v) { tree.insert(a, k, v) })
}

// return the map keeping only keys in the list
/// Creates a new map containing only the key-value pairs from the original map
/// where the keys are present in the given list of desired keys.
pub fn take(from map: Map(k, v), keeping desired: List(k)) -> Map(k, v) {
case desired {
[x, ..xs] ->
case tree.find(map, x) {
Ok(x) -> tree.insert(take(map, xs), x.0, x.1)
Ok(v) -> tree.insert(take(map, xs), x, v)
Error(_) -> take(map, xs)
}
[] -> tree.clear(map)
}
}

/// Creates a new map from a list of key-value pairs and a comparison function
/// for keys.
pub fn from_list(
members: List(#(k, v)),
compare: fn(k, k) -> Order,
Expand All @@ -74,6 +97,7 @@ pub fn from_list(
})
}

/// Converts the map to a list of key-value pairs.
pub fn to_list(map: Map(k, v)) -> List(#(k, v)) {
tree.foldr(map, [], fn(a, k, v) { [#(k, v), ..a] })
}
18 changes: 18 additions & 0 deletions src/gleamy/non_empty_list.gleam
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
//// This module provides a type and functions for working with non-empty lists,
//// which are lists that are guaranteed to have at least one element.

import gleam/list

/// The `NonEmptyList(a)` type represents a non-empty list of elements of type `a`.
/// It has two cases: `End(first: a)` for a list with a single element, and
/// `Next(first: a, rest: NonEmptyList(a))` for a list with multiple elements.
pub type NonEmptyList(a) {
End(first: a)
Next(first: a, rest: NonEmptyList(a))
}

/// Applies a function to every element in the non-empty list, accumulating
/// the results with the provided initial accumulator value.
pub fn fold(
over list: NonEmptyList(a),
from initial: b,
Expand All @@ -16,10 +24,14 @@ pub fn fold(
}
}

/// Returns the count (number of elements) in the non-empty list.
/// Time complexity: O(n)
pub fn count(list: NonEmptyList(a)) -> Int {
fold(list, 0, fn(acc, _) { acc + 1 })
}

/// Applies a transformation function to every element in the non-empty list
/// and returns a new non-empty list with the transformed elements.
pub fn map(list: NonEmptyList(a), transform: fn(a) -> b) -> NonEmptyList(b) {
case list {
End(x) -> End(transform(x))
Expand All @@ -29,6 +41,8 @@ pub fn map(list: NonEmptyList(a), transform: fn(a) -> b) -> NonEmptyList(b) {
}
}

/// Filters the elements of the non-empty list based on a predicate function
/// and returns a (potentially empty) list with the elements that satisfy the predicate.
pub fn filter(list: NonEmptyList(a), predicate: fn(a) -> Bool) -> List(a) {
fold(list, [], fn(acc, item) {
case predicate(item) {
Expand All @@ -39,18 +53,22 @@ pub fn filter(list: NonEmptyList(a), predicate: fn(a) -> Bool) -> List(a) {
|> list.reverse()
}

/// Converts the non-empty list to a regular (potentially empty) list.
pub fn to_list(list: NonEmptyList(a)) -> List(a) {
fold(list, [], fn(acc, item) { [item, ..acc] })
|> list.reverse()
}

/// Tries to create a non-empty list from a regular (potentially empty) list.
/// Returns an error if the input list is empty.
pub fn from_list(list: List(a)) -> Result(NonEmptyList(a), Nil) {
case list {
[] -> Error(Nil)
[x, ..xs] -> Ok(list.fold(xs, End(x), fn(acc, item) { Next(item, acc) }))
}
}

/// Reverses the order of elements in the non-empty list.
pub fn reverse(list: NonEmptyList(a)) -> NonEmptyList(a) {
case list {
End(_) -> list
Expand Down
62 changes: 43 additions & 19 deletions src/gleamy/pairing_heap.gleam
Original file line number Diff line number Diff line change
@@ -1,55 +1,79 @@
//// This module provides an implementation of the pairing heap data structure,
//// a type of self-adjusting heap with efficient insert, find_min, and delete_min, and merge operations.

// Based on "Purely Functional Data Structures" by Okasaki (1998)

import gleam/order.{type Order, Gt}

type T(a) {
E
T(a, List(T(a)))
type Tree(a) {
Empty
Tree(a, List(Tree(a)))
}

pub opaque type Heap(a) {
Heap(root: T(a), compare: fn(a, a) -> Order)
Heap(root: Tree(a), compare: fn(a, a) -> Order)
}

/// Creates a new empty heap with the provided comparison function.
pub fn new(compare: fn(a, a) -> Order) -> Heap(a) {
Heap(E, compare)
Heap(Empty, compare)
}

/// Inserts a new item into the heap, preserving the heap property.
/// Time complexity: O(1)
pub fn insert(heap: Heap(a), key: a) -> Heap(a) {
Heap(merge(T(key, []), heap.root, heap.compare), heap.compare)
Heap(merge_trees(Tree(key, []), heap.root, heap.compare), heap.compare)
}

/// Returns the minimum element in the heap, if the heap is not empty.
/// Time complexity: O(1)
pub fn find_min(heap: Heap(a)) -> Result(a, Nil) {
case heap.root {
T(x, _) -> Ok(x)
E -> Error(Nil)
Tree(x, _) -> Ok(x)
Empty -> Error(Nil)
}
}

/// Removes and returns the minimum element from the heap along with the
/// new heap after deletion, if the heap is not empty.
/// Time complexity: O(log n) amortized
pub fn delete_min(heap: Heap(a)) -> Result(#(a, Heap(a)), Nil) {
case heap.root {
T(x, xs) -> Ok(#(x, Heap(merge_pairs(xs, heap.compare), heap.compare)))
E -> Error(Nil)
Tree(x, xs) -> Ok(#(x, Heap(merge_pairs(xs, heap.compare), heap.compare)))
Empty -> Error(Nil)
}
}

fn merge(x: T(a), y: T(a), compare: fn(a, a) -> Order) -> T(a) {
/// Merges two heaps into a new heap containing all elements from both heaps,
/// preserving the heap property.
/// The given heaps must have the same comparison function.
/// Time complexity: O(1)
pub fn merge(heap1: Heap(a), heap2: Heap(a)) -> Heap(a) {
let compare = heap1.compare
Heap(merge_trees(heap1.root, heap2.root, compare), compare)
}

fn merge_trees(x: Tree(a), y: Tree(a), compare: fn(a, a) -> Order) -> Tree(a) {
case x, y {
x, E -> x
E, y -> y
T(xk, xs), T(yk, ys) ->
x, Empty -> x
Empty, y -> y
Tree(xk, xs), Tree(yk, ys) ->
case compare(xk, yk) {
Gt -> T(yk, [x, ..ys])
_ -> T(xk, [y, ..xs])
Gt -> Tree(yk, [x, ..ys])
_ -> Tree(xk, [y, ..xs])
}
}
}

fn merge_pairs(l: List(T(a)), compare: fn(a, a) -> Order) -> T(a) {
fn merge_pairs(l: List(Tree(a)), compare: fn(a, a) -> Order) -> Tree(a) {
case l {
[] -> E
[] -> Empty
[h] -> h
[h1, h2, ..hs] ->
merge(merge(h1, h2, compare), merge_pairs(hs, compare), compare)
merge_trees(
merge_trees(h1, h2, compare),
merge_pairs(hs, compare),
compare,
)
}
}
Loading

0 comments on commit 8344aec

Please sign in to comment.