From 87bd77aecbd7c4b1b91700af10ff1c1257008e75 Mon Sep 17 00:00:00 2001 From: Julian Schurhammer Date: Fri, 3 May 2024 09:53:06 +1200 Subject: [PATCH] fully tested --- README.md | 71 ++++++++++++-- gleam.toml | 2 +- src/gleamy/red_black_tree_map.gleam | 20 ---- src/gleamy/red_black_tree_set.gleam | 16 ---- test/leftist_heap_test.gleam | 53 +++++++++++ test/pairing_heap_test.gleam | 53 +++++++++++ test/red_black_tree_map_test.gleam | 72 +++++++++++++++ test/red_black_tree_set_test.gleam | 137 ++++++++++++++++++++++++++++ 8 files changed, 379 insertions(+), 45 deletions(-) create mode 100644 test/leftist_heap_test.gleam create mode 100644 test/pairing_heap_test.gleam create mode 100644 test/red_black_tree_map_test.gleam create mode 100644 test/red_black_tree_set_test.gleam diff --git a/README.md b/README.md index 4d7b812..2a68190 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,72 @@ Data structures in pure Gleam. -## Quick start +## Supported Structures + +### 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). + + +### Heap + +These heaps are min-heaps, providing efficient access to the minimum value based on a given comparison function. + + +`gleamy/pairing_heap`: + +This is the recommended heap structure for most use cases. However, for some non-linear use cases the performance can degrade because of the amortized nature of this structure. + + +`gleamy/leftist_heap`: + +This heap structure has consistent performance across all use cases. + + +### Non-Empty List + + +`gleamy/non_empty_list`: + +Non-Empty list is a list structure that always contains at least one item. + + +### Map + +Maps are used for key-value lookups. Keys are compared with a user-provided comparison function. + + +`gleamy/map`: + +This is a wrapper around `red_black_tree_map` providing additional utility functions. + + +`gleamy/red_black_tree_map`: + +A map based on a red-black balanced tree structure. + + +### Set + +Sets are used to store a collection of items. Items are compared with a user-provided comparison function to remove duplicate values. + + +`gleamy/set`: + +This is a wrapper around `red_black_tree_set` providing additional utility functions. + + +`gleamy/red_black_tree_map`: + +A set based on a red-black balanced tree structure. -```sh -gleam run # Run the project -gleam test # Run the tests -gleam shell # Run an Erlang shell -``` ## Installation -If available on Hex this package can be added to your Gleam project: +This package can be added to your Gleam project: ```sh gleam add gleamy_structures @@ -24,4 +79,4 @@ gleam add gleamy_structures and its documentation can be found at . ## Contributions Welcome -Feel free to make PRs, issues, or requests for new data structures and functions :) Especially welcome would be more rigorous tests and reviews of the implementation compared to source materials. +Feel free to make PRs, issues, or requests for new data structures and functions :) diff --git a/gleam.toml b/gleam.toml index 88df2a1..4b777a7 100644 --- a/gleam.toml +++ b/gleam.toml @@ -4,7 +4,7 @@ version = "0.5.0" # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. licences = ["Apache-2.0"] -description = "Data structures in pure Gleam including tree, heap, non empty list, and priority queue." +description = "Data structures in pure Gleam including tree, heap, non empty list, map, set, and priority queue." repository = { type = "github", user = "schurhammer", repo = "gleamy_structures" } links = [{ title = "Website", href = "https://github.com/schurhammer/gleamy_structures" }] diff --git a/src/gleamy/red_black_tree_map.gleam b/src/gleamy/red_black_tree_map.gleam index ef25c2b..1e8b98b 100644 --- a/src/gleamy/red_black_tree_map.gleam +++ b/src/gleamy/red_black_tree_map.gleam @@ -46,10 +46,6 @@ pub fn foldr(tree: Tree(k, v), acc: b, fun: fn(b, k, v) -> b) -> b { do_foldr(tree.root, acc, fun) } -pub fn draw(tree: Tree(k, v), to_string: fn(k, v) -> String) -> String { - do_draw(tree.root, 0, to_string) -} - fn ins(node: Node(k, v), x: #(k, v), compare: fn(k, k) -> Order) -> Node(k, v) { case node { E -> T(R, E, x, E) @@ -214,19 +210,3 @@ fn do_indent(acc: String, i: Int) -> String { i -> do_indent(". " <> acc, i - 1) } } - -fn do_draw( - node: Node(k, v), - indent: Int, - to_string: fn(k, v) -> String, -) -> String { - case node { - T(_, l, k, r) -> { - let ls = do_draw(l, indent + 1, to_string) - let ks = do_indent(to_string(k.0, k.1) <> "\n", indent) - let rs = do_draw(r, indent + 1, to_string) - ls <> ks <> rs - } - _ -> "" - } -} diff --git a/src/gleamy/red_black_tree_set.gleam b/src/gleamy/red_black_tree_set.gleam index 7ccfb7f..a4493ec 100644 --- a/src/gleamy/red_black_tree_set.gleam +++ b/src/gleamy/red_black_tree_set.gleam @@ -46,10 +46,6 @@ pub fn foldr(tree: Tree(a), acc: b, fun: fn(b, a) -> b) -> b { do_foldr(tree.root, acc, fun) } -pub fn draw(tree: Tree(a), to_string: fn(a) -> String) { - do_draw(tree.root, 0, to_string) -} - fn ins(node, x, compare) { case node { E -> T(R, E, x, E) @@ -210,15 +206,3 @@ fn do_indent(acc, i) { i -> do_indent(". " <> acc, i - 1) } } - -fn do_draw(node, indent, to_string) { - case node { - T(_, l, k, r) -> { - let ls = do_draw(l, indent + 1, to_string) - let ks = do_indent(to_string(k) <> "\n", indent) - let rs = do_draw(r, indent + 1, to_string) - ls <> ks <> rs - } - _ -> "" - } -} diff --git a/test/leftist_heap_test.gleam b/test/leftist_heap_test.gleam new file mode 100644 index 0000000..782726a --- /dev/null +++ b/test/leftist_heap_test.gleam @@ -0,0 +1,53 @@ +import gleam/int +import gleamy/pairing_heap as heap +import gleeunit/should + +pub fn find_test() { + let heap = heap.new(int.compare) + + heap.find_min(heap) + |> should.equal(Error(Nil)) + + let heap = heap.insert(heap, 3) + + heap.find_min(heap) + |> should.equal(Ok(3)) + + let heap = heap.insert(heap, 1) + + heap.find_min(heap) + |> should.equal(Ok(1)) + + let heap = heap.insert(heap, 4) + + heap.find_min(heap) + |> should.equal(Ok(1)) +} + +pub fn delete_test() { + let heap = + heap.new(int.compare) + |> heap.insert(3) + |> heap.insert(1) + |> heap.insert(4) + |> heap.insert(1) + |> heap.insert(5) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 1) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 1) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 3) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 4) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 5) + + heap.delete_min(heap) + |> should.equal(Error(Nil)) +} diff --git a/test/pairing_heap_test.gleam b/test/pairing_heap_test.gleam new file mode 100644 index 0000000..b1a18cc --- /dev/null +++ b/test/pairing_heap_test.gleam @@ -0,0 +1,53 @@ +import gleam/int +import gleamy/leftist_heap as heap +import gleeunit/should + +pub fn find_test() { + let heap = heap.new(int.compare) + + heap.find_min(heap) + |> should.equal(Error(Nil)) + + let heap = heap.insert(heap, 3) + + heap.find_min(heap) + |> should.equal(Ok(3)) + + let heap = heap.insert(heap, 1) + + heap.find_min(heap) + |> should.equal(Ok(1)) + + let heap = heap.insert(heap, 4) + + heap.find_min(heap) + |> should.equal(Ok(1)) +} + +pub fn delete_test() { + let heap = + heap.new(int.compare) + |> heap.insert(3) + |> heap.insert(1) + |> heap.insert(4) + |> heap.insert(1) + |> heap.insert(5) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 1) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 1) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 3) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 4) + + let assert Ok(#(x, heap)) = heap.delete_min(heap) + should.equal(x, 5) + + heap.delete_min(heap) + |> should.equal(Error(Nil)) +} diff --git a/test/red_black_tree_map_test.gleam b/test/red_black_tree_map_test.gleam new file mode 100644 index 0000000..51e06c3 --- /dev/null +++ b/test/red_black_tree_map_test.gleam @@ -0,0 +1,72 @@ +import gleam/string +import gleamy/red_black_tree_map as map +import gleeunit/should + +pub fn insert_and_find_test() { + let map = map.new(string.compare) + let updated_map = + map + |> map.insert("key1", "value1") + |> map.insert("key2", "value2") + + updated_map + |> map.find("key1") + |> should.equal(Ok(#("key1", "value1"))) + + updated_map + |> map.find("key2") + |> should.equal(Ok(#("key2", "value2"))) +} + +pub fn delete_test() { + let map = + map.new(string.compare) + |> map.insert("key1", "value1") + |> map.insert("key2", "value2") + + let updated_map = + map + |> map.delete("key1") + + updated_map + |> map.find("key1") + |> should.equal(Error(Nil)) + + updated_map + |> map.find("key2") + |> should.equal(Ok(#("key2", "value2"))) +} + +pub fn fold_test() { + let map = + map.new(string.compare) + |> map.insert("key1", "value1") + |> map.insert("key2", "value2") + |> map.insert("key3", "value3") + + let result = + map + |> map.fold(0, fn(a, _, _) { a + 1 }) + + result + |> should.equal(3) +} + +pub fn missing_keys_test() { + let map = + map.new(string.compare) + |> map.insert("key1", "value1") + |> map.insert("key2", "value2") + + map + |> map.find("key3") + |> should.equal(Error(Nil)) + + let updated_map = + map + |> map.delete("key3") + + updated_map + |> map.find("key3") + |> should.equal(Error(Nil)) +} diff --git a/test/red_black_tree_set_test.gleam b/test/red_black_tree_set_test.gleam new file mode 100644 index 0000000..9cb27e1 --- /dev/null +++ b/test/red_black_tree_set_test.gleam @@ -0,0 +1,137 @@ +import gleam/int +import gleamy/red_black_tree_set as set +import gleeunit/should + +pub fn find_test() { + let set = set.new(int.compare) + let updated_set = + set + |> set.insert(1) + |> set.insert(2) + + updated_set + |> set.find(1) + |> should.equal(Ok(1)) + + updated_set + |> set.find(3) + |> should.equal(Error(Nil)) +} + +pub fn delete_test() { + let set = set.new(int.compare) + let updated_set = + set + |> set.insert(1) + |> set.insert(2) + + let result_set = + updated_set + |> set.delete(1) + + result_set + |> set.find(1) + |> should.equal(Error(Nil)) +} + +pub fn fold_test() { + let set = set.new(int.compare) + let updated_set = + set + |> set.insert(1) + |> set.insert(2) + |> set.insert(3) + + let sum = + updated_set + |> set.fold(0, fn(acc, x) { acc + x }) + + sum + |> should.equal(6) +} + +pub fn insert_test() { + let set = set.new(int.compare) + let updated_set = + set + |> set.insert(1) + + updated_set + |> set.find(1) + |> should.equal(Ok(1)) +} + +pub fn new_test() { + let set = set.new(int.compare) + + set + |> set.find(1) + |> should.equal(Error(Nil)) +} + +fn to_list(set) -> List(a) { + set.foldr(set, [], fn(a, i) { [i, ..a] }) +} + +pub fn insert_and_remove_test() { + let set = set.new(int.compare) + + // Insert initial elements + let updated_set = + set + |> set.insert(1) + |> set.insert(2) + |> set.insert(3) + |> set.insert(4) + |> set.insert(5) + + updated_set + |> to_list() + |> should.equal([1, 2, 3, 4, 5]) + + // Remove an element + let removed_set = + updated_set + |> set.delete(2) + + removed_set + |> to_list() + |> should.equal([1, 3, 4, 5]) + + // Insert additional elements + let final_set = + removed_set + |> set.insert(6) + |> set.insert(7) + |> set.insert(8) + |> set.insert(9) + |> set.insert(10) + + final_set + |> to_list() + |> should.equal([1, 3, 4, 5, 6, 7, 8, 9, 10]) + + // Remove multiple elements + let updated_set2 = + final_set + |> set.delete(3) + |> set.delete(6) + |> set.delete(9) + + updated_set2 + |> to_list() + |> should.equal([1, 4, 5, 7, 8, 10]) + + // Insert and remove elements + let final_set2 = + updated_set2 + |> set.insert(11) + |> set.insert(12) + |> set.delete(4) + |> set.insert(13) + |> set.delete(10) + + final_set2 + |> to_list() + |> should.equal([1, 5, 7, 8, 11, 12, 13]) +}