From 7885da4e2979ef560e99ed05dff00da9f6a9adaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20W=C3=BChr?= Date: Thu, 19 Sep 2024 09:09:22 +0200 Subject: [PATCH] feat: implement stack --- manifest.toml | 11 +++++ src/fortheck.gleam | 5 --- src/fortheck/error.gleam | 4 ++ src/fortheck/stack.gleam | 61 ++++++++++++++++++++++++++ test/fortheck_test.gleam | 7 --- test/stack_test.gleam | 92 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 168 insertions(+), 12 deletions(-) create mode 100644 manifest.toml delete mode 100644 src/fortheck.gleam create mode 100644 src/fortheck/error.gleam create mode 100644 src/fortheck/stack.gleam create mode 100644 test/stack_test.gleam diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..e851620 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,11 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, + { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, +] + +[requirements] +gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/src/fortheck.gleam b/src/fortheck.gleam deleted file mode 100644 index 5299693..0000000 --- a/src/fortheck.gleam +++ /dev/null @@ -1,5 +0,0 @@ -import gleam/io - -pub fn main() { - io.println("Hello from fortheck!") -} diff --git a/src/fortheck/error.gleam b/src/fortheck/error.gleam new file mode 100644 index 0000000..f1ee495 --- /dev/null +++ b/src/fortheck/error.gleam @@ -0,0 +1,4 @@ +pub type Error { + StackOverflow + StackUnderflow +} diff --git a/src/fortheck/stack.gleam b/src/fortheck/stack.gleam new file mode 100644 index 0000000..321b679 --- /dev/null +++ b/src/fortheck/stack.gleam @@ -0,0 +1,61 @@ +import fortheck/error.{type Error, StackOverflow, StackUnderflow} +import gleam/bool +import gleam/list +import gleam/result + +pub opaque type Stack(a) { + Stack(capacity: Int, length: Int, data: List(a)) +} + +pub fn new() -> Stack(a) { + Stack(131_072, 0, []) +} + +pub fn from_list(list: List(a)) -> Result(Stack(a), Error) { + list.try_fold(over: list, from: new(), with: push) +} + +pub fn to_list(stack: Stack(a)) -> List(a) { + list.reverse(stack.data) +} + +pub fn push(onto stack: Stack(a), this item: a) -> Result(Stack(a), Error) { + use <- bool.guard( + when: stack.length >= stack.capacity, + return: Error(StackOverflow), + ) + + Ok(Stack(..stack, length: stack.length + 1, data: [item, ..stack.data])) +} + +pub fn pop(from stack: Stack(a)) -> Result(#(a, Stack(a)), Error) { + case stack.data { + [] -> Error(StackUnderflow) + [x, ..xs] -> Ok(#(x, Stack(..stack, length: stack.length - 1, data: xs))) + } +} + +pub fn try_pop( + from stack: Stack(a), + apply fun: fn(a, Stack(a)) -> Result(b, Error), +) -> Result(b, Error) { + use #(item, stack) <- result.try(pop(from: stack)) + + fun(item, stack) +} + +pub fn pop_2(from stack: Stack(a)) -> Result(#(#(a, a), Stack(a)), Error) { + use b, stack <- try_pop(from: stack) + use a, stack <- try_pop(from: stack) + + Ok(#(#(a, b), stack)) +} + +pub fn try_pop_2( + from stack: Stack(a), + apply fun: fn(a, a, Stack(a)) -> Result(b, Error), +) -> Result(b, Error) { + use #(#(a, b), stack) <- result.try(pop_2(from: stack)) + + fun(a, b, stack) +} diff --git a/test/fortheck_test.gleam b/test/fortheck_test.gleam index 3831e7a..ecd12ad 100644 --- a/test/fortheck_test.gleam +++ b/test/fortheck_test.gleam @@ -1,12 +1,5 @@ import gleeunit -import gleeunit/should pub fn main() { gleeunit.main() } - -// gleeunit test functions end in `_test` -pub fn hello_world_test() { - 1 - |> should.equal(1) -} diff --git a/test/stack_test.gleam b/test/stack_test.gleam new file mode 100644 index 0000000..f80dcab --- /dev/null +++ b/test/stack_test.gleam @@ -0,0 +1,92 @@ +import fortheck/error +import fortheck/stack +import gleam/iterator +import gleam/pair +import gleam/result +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +pub fn new_test() { + stack.new() + |> stack.to_list + |> should.equal([]) +} + +pub fn push_test() { + stack.new() + |> stack.push(123) + |> should.be_ok + |> stack.push(456) + |> should.be_ok + |> stack.to_list + |> should.equal([123, 456]) +} + +pub fn push_stack_overflow_test() { + iterator.range(1, 131_072) + |> iterator.try_fold(stack.new(), stack.push) + |> should.be_ok + |> stack.push(1) + |> should.be_error + |> should.equal(error.StackOverflow) +} + +pub fn from_list_test() { + [123, 456] + |> stack.from_list + |> should.be_ok + |> stack.to_list + |> should.equal([123, 456]) +} + +pub fn pop_stack_underflow_test() { + stack.new() + |> stack.pop + |> should.be_error + |> should.equal(error.StackUnderflow) +} + +pub fn pop_test() { + let assert Ok(stack) = stack.from_list([1, 2, 3]) + + stack + |> stack.pop + |> should.be_ok + |> pair.first + |> should.equal(3) + + { + use a, _ <- stack.try_pop(stack) + Ok(a) + } + |> should.be_ok + |> should.equal(3) +} + +pub fn pop_2_test() { + let assert Ok(stack) = stack.from_list([1, 2, 3]) + + stack + |> stack.pop_2 + |> should.be_ok + |> pair.first + |> should.equal(#(2, 3)) + + { + use a, b, _ <- stack.try_pop_2(stack) + Ok(#(a, b)) + } + |> should.be_ok + |> should.equal(#(2, 3)) +} + +pub fn pop_2_stack_underflow_test() { + stack.from_list([1]) + |> result.try(stack.pop_2) + |> should.be_error + |> should.equal(error.StackUnderflow) +}