diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..86db220 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "27.1.2" + gleam-version: "1.6.1" + rebar3-version: "3" + # elixir-version: "1.15.4" + - run: gleam deps download + - run: gleam test --target erlang + - run: gleam test --target javascript + - run: gleam format --check src test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4737723 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## v1.0.0 - 2024-11-20 + +- Initial release! Extracted from `gleam_stdlib` v0.43.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..74a2823 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# dequeue + +[![Package Version](https://img.shields.io/hexpm/v/dequeue)](https://hex.pm/packages/dequeue) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/dequeue/) + +```sh +gleam add dequeue@1 +``` +```gleam +import dequeue + +pub fn main() { + // TODO: An example of the project in use +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +``` diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..e154cb2 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,16 @@ +name = "deque" +version = "1.0.0" +gleam = ">= 1.0.0" +licences = ["Apache-2.0"] +description = "A double-ended queue data structure" + +repository = { type = "github", user = "gleam-lang", repo = "deque" } +links = [ + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = ">= 0.34.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..4721e5c --- /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.43.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "69EF22E78FDCA9097CBE7DF91C05B2A8B5436826D9F66680D879182C0860A747" }, + { 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/deque.gleam b/src/deque.gleam new file mode 100644 index 0000000..e40e5ab --- /dev/null +++ b/src/deque.gleam @@ -0,0 +1,291 @@ +import gleam/list + +/// A deque (double-ended-queue) is an ordered collection of elements. It is +/// similar to a list. +/// Unlike a list elements can be added to or removed from either the front or +/// the back in a performant fashion. +/// +/// The internal representation may be different for two deques with the same +/// elements in the same order if the deques were constructed in different +/// ways. This is the price paid for a deque's fast access at both the front +/// and the back. +/// +/// Because of unpredictable internal representation the equality operator `==` +/// may return surprising results, and the `is_equal` and `is_logically_equal` +/// functions are the recommended way to test deques for equality. +/// +pub opaque type Deque(a) { + Deque(in: List(a), out: List(a)) +} + +/// Creates a fresh deque that contains no values. +/// +pub fn new() -> Deque(a) { + Deque(in: [], out: []) +} + +/// Converts a list of elements into a deque of the same elements in the same +/// order. The first element in the list becomes the front element in the deque. +/// +/// This function runs in constant time. +/// +/// # Examples +/// +/// ```gleam +/// [1, 2, 3] |> from_list |> length +/// // -> 3 +/// ``` +/// +pub fn from_list(list: List(a)) -> Deque(a) { + Deque(in: [], out: list) +} + +/// Converts a deque of elements into a list of the same elements in the same +/// order. The front element in the deque becomes the first element in the list. +/// +/// This function runs in linear time. +/// +/// # Examples +/// +/// ```gleam +/// new() |> push_back(1) |> push_back(2) |> to_list +/// // -> [1, 2] +/// ``` +/// +pub fn to_list(deque: Deque(a)) -> List(a) { + deque.out + |> list.append(list.reverse(deque.in)) +} + +/// Determines whether or not the deque is empty. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// [] |> from_list |> is_empty +/// // -> True +/// ``` +/// +/// ```gleam +/// [1] |> from_list |> is_empty +/// // -> False +/// ``` +/// +/// ```gleam +/// [1, 2] |> from_list |> is_empty +/// // -> False +/// ``` +/// +pub fn is_empty(deque: Deque(a)) -> Bool { + deque.in == [] && deque.out == [] +} + +/// Counts the number of elements in a given deque. +/// +/// This function has to traverse the deque to determine the number of elements, +/// so it runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// length(from_list([])) +/// // -> 0 +/// ``` +/// +/// ```gleam +/// length(from_list([1])) +/// // -> 1 +/// ``` +/// +/// ```gleam +/// length(from_list([1, 2])) +/// // -> 2 +/// ``` +/// +pub fn length(deque: Deque(a)) -> Int { + list.length(deque.in) + list.length(deque.out) +} + +/// Pushes an element onto the back of the deque. +/// +/// # Examples +/// +/// ```gleam +/// [1, 2] |> from_list |> push_back(3) |> to_list +/// // -> [1, 2, 3] +/// ``` +/// +pub fn push_back(onto deque: Deque(a), this item: a) -> Deque(a) { + Deque(in: [item, ..deque.in], out: deque.out) +} + +/// Pushes an element onto the front of the deque. +/// +/// # Examples +/// +/// ```gleam +/// [0, 0] |> from_list |> push_front(1) |> to_list +/// // -> [1, 0, 0] +/// ``` +/// +pub fn push_front(onto deque: Deque(a), this item: a) -> Deque(a) { + Deque(in: deque.in, out: [item, ..deque.out]) +} + +/// Gets the last element from the deque, returning the +/// element and a new deque without that element. +/// +/// This function typically runs in constant time, but will occasionally run in +/// linear time. +/// +/// # Examples +/// +/// ```gleam +/// new() +/// |> push_back(0) +/// |> push_back(1) +/// |> pop_back +/// // -> Ok(#(1, push_front(new(), 0))) +/// ``` +/// +/// ```gleam +/// new() +/// |> push_front(0) +/// |> pop_back +/// // -> Ok(#(0, new())) +/// ``` +/// +/// ```gleam +/// new() |> pop_back +/// // -> Error(Nil) +/// ``` +/// +pub fn pop_back(from deque: Deque(a)) -> Result(#(a, Deque(a)), Nil) { + case deque { + Deque(in: [], out: []) -> Error(Nil) + Deque(in: [], out: out) -> pop_back(Deque(in: list.reverse(out), out: [])) + Deque(in: [first, ..rest], out: out) -> { + let deque = Deque(in: rest, out: out) + Ok(#(first, deque)) + } + } +} + +/// Gets the first element from the deque, returning the +/// element and a new deque without that element. +/// +/// This function typically runs in constant time, but will occasionally run in +/// linear time. +/// +/// # Examples +/// +/// ```gleam +/// new() +/// |> push_front(1) +/// |> push_front(0) +/// |> pop_front +/// // -> Ok(#(0, push_back(new(), 1))) +/// ``` +/// +/// ```gleam +/// new() +/// |> push_back(0) +/// |> pop_front +/// // -> Ok(#(0, new())) +/// ``` +/// +/// ```gleam +/// new() |> pop_back +/// // -> Error(Nil) +/// ``` +/// +pub fn pop_front(from deque: Deque(a)) -> Result(#(a, Deque(a)), Nil) { + case deque { + Deque(in: [], out: []) -> Error(Nil) + Deque(in: in, out: []) -> pop_front(Deque(in: [], out: list.reverse(in))) + Deque(in: in, out: [first, ..rest]) -> { + let deque = Deque(in: in, out: rest) + Ok(#(first, deque)) + } + } +} + +/// Creates a new deque from a given deque containing the same elements, but in +/// the opposite order. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// [] |> from_list |> reverse |> to_list +/// // -> [] +/// ``` +/// +/// ```gleam +/// [1] |> from_list |> reverse |> to_list +/// // -> [1] +/// ``` +/// +/// ```gleam +/// [1, 2] |> from_list |> reverse |> to_list +/// // -> [2, 1] +/// ``` +/// +pub fn reverse(deque: Deque(a)) -> Deque(a) { + Deque(in: deque.out, out: deque.in) +} + +/// Checks whether two deques have equal elements in the same order, where the +/// equality of elements is determined by a given equality checking function. +/// +/// This function is useful as the internal representation may be different for +/// two deques with the same elements in the same order depending on how they +/// were constructed, so the equality operator `==` may return surprising +/// results. +/// +/// This function runs in linear time multiplied by the time taken by the +/// element equality checking function. +/// +pub fn is_logically_equal( + a: Deque(a), + to b: Deque(a), + checking element_is_equal: fn(a, a) -> Bool, +) -> Bool { + check_equal(a.out, a.in, b.out, b.in, element_is_equal) +} + +fn check_equal( + xs: List(a), + x_tail: List(a), + ys: List(a), + y_tail: List(a), + eq: fn(a, a) -> Bool, +) -> Bool { + case xs, x_tail, ys, y_tail { + [], [], [], [] -> True + [x, ..xs], _, [y, ..ys], _ -> + case eq(x, y) { + False -> False + True -> check_equal(xs, x_tail, ys, y_tail, eq) + } + [], [_, ..], _, _ -> check_equal(list.reverse(x_tail), [], ys, y_tail, eq) + _, _, [], [_, ..] -> check_equal(xs, x_tail, list.reverse(y_tail), [], eq) + _, _, _, _ -> False + } +} + +/// Checks whether two deques have the same elements in the same order. +/// +/// This function is useful as the internal representation may be different for +/// two deques with the same elements in the same order depending on how they +/// were constructed, so the equality operator `==` may return surprising +/// results. +/// +/// This function runs in linear time. +/// +pub fn is_equal(a: Deque(a), to b: Deque(a)) -> Bool { + check_equal(a.out, a.in, b.out, b.in, fn(a, b) { a == b }) +} diff --git a/test/deque_test.gleam b/test/deque_test.gleam new file mode 100644 index 0000000..55c0e2d --- /dev/null +++ b/test/deque_test.gleam @@ -0,0 +1,347 @@ +import deque +import gleam/int +import gleam/list +import gleam/pair +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +pub fn from_and_to_list_test() { + deque.from_list([]) + |> should.equal(deque.new()) + + [1] + |> deque.from_list + |> deque.to_list + |> should.equal([1]) + + [1, 2] + |> deque.from_list + |> deque.to_list + |> should.equal([1, 2]) + + [1, 2, 3] + |> deque.from_list + |> deque.to_list + |> should.equal([1, 2, 3]) +} + +pub fn is_empty_test() { + deque.new() + |> deque.is_empty + |> should.be_true + + deque.from_list([""]) + |> deque.is_empty + |> should.be_false +} + +pub fn length_test() { + let testcase = fn(input) { + deque.from_list(input) + |> deque.length + |> should.equal(list.length(input)) + } + + testcase([]) + testcase([1]) + testcase([1, 2]) + testcase([1, 2, 1]) + testcase([1, 2, 1, 5, 2, 7, 2, 7, 8, 4, 545]) +} + +pub fn push_back_test() { + [1, 2] + |> deque.from_list + |> deque.push_back(3) + |> deque.to_list + |> should.equal([1, 2, 3]) + + deque.new() + |> deque.push_back(1) + |> deque.push_back(2) + |> deque.push_back(3) + |> deque.to_list + |> should.equal([1, 2, 3]) +} + +pub fn push_front_test() { + [2, 3] + |> deque.from_list + |> deque.push_front(1) + |> deque.push_front(0) + |> deque.to_list + |> should.equal([0, 1, 2, 3]) +} + +pub fn push_test() { + deque.new() + |> deque.push_front("b") + |> deque.push_back("x") + |> deque.push_front("a") + |> deque.push_back("y") + |> deque.to_list + |> should.equal(["a", "b", "x", "y"]) +} + +pub fn pop_back_test() { + let assert Ok(tup) = + [1, 2, 3] + |> deque.from_list + |> deque.pop_back + + tup + |> pair.first + |> should.equal(3) + + tup + |> pair.second + |> deque.is_equal(deque.from_list([1, 2])) + |> should.be_true +} + +pub fn pop_back_after_push_back_test() { + let assert Ok(tup) = + deque.new() + |> deque.push_back(1) + |> deque.push_back(2) + |> deque.push_back(3) + |> deque.pop_back + + tup + |> pair.first + |> should.equal(3) +} + +pub fn pop_back_after_push_test() { + let assert Ok(tup) = + deque.new() + |> deque.push_front("b") + |> deque.push_back("x") + |> deque.push_front("a") + |> deque.push_back("y") + |> deque.pop_back + + tup + |> pair.first + |> should.equal("y") +} + +pub fn pop_back_empty_test() { + deque.from_list([]) + |> deque.pop_back + |> should.equal(Error(Nil)) +} + +pub fn pop_front_test() { + let assert Ok(tup) = + [1, 2, 3] + |> deque.from_list + |> deque.pop_front + + tup + |> pair.first + |> should.equal(1) + + tup + |> pair.second + |> deque.is_equal(deque.from_list([2, 3])) + |> should.be_true +} + +pub fn pop_front_after_push_front_test() { + let assert Ok(tup) = + deque.new() + |> deque.push_front(3) + |> deque.push_front(2) + |> deque.push_front(1) + |> deque.pop_front + + tup + |> pair.first + |> should.equal(1) +} + +pub fn pop_front_after_push_test() { + let assert Ok(tup) = + deque.new() + |> deque.push_front("b") + |> deque.push_back("x") + |> deque.push_front("a") + |> deque.pop_front + + tup + |> pair.first + |> should.equal("a") +} + +pub fn pop_front_empty_test() { + deque.from_list([]) + |> deque.pop_front + |> should.equal(Error(Nil)) +} + +pub fn reverse_test() { + deque.from_list([1, 2, 3]) + |> deque.reverse + |> deque.to_list + |> should.equal([3, 2, 1]) + + deque.new() + |> deque.push_back(1) + |> deque.push_back(2) + |> deque.push_back(3) + |> deque.reverse + |> deque.to_list + |> should.equal([3, 2, 1]) + + deque.new() + |> deque.push_front(1) + |> deque.push_front(2) + |> deque.push_front(3) + |> deque.reverse + |> deque.to_list + |> should.equal([1, 2, 3]) + + deque.new() + |> deque.push_front(1) + |> deque.push_front(2) + |> deque.push_back(3) + |> deque.push_back(4) + |> deque.reverse + |> deque.to_list + |> should.equal([4, 3, 1, 2]) +} + +pub fn is_equal_test() { + let should_equal = fn(a, b) { + a + |> deque.is_equal(to: b) + |> should.be_true + } + + let should_not_equal = fn(a, b) { + a + |> deque.is_equal(to: b) + |> should.be_false + } + + should_equal(deque.new(), deque.new()) + + deque.new() + |> deque.push_front(1) + |> should_equal( + deque.new() + |> deque.push_back(1), + ) + + deque.new() + |> deque.push_front(1) + |> should_equal( + deque.new() + |> deque.push_front(1), + ) + + deque.new() + |> deque.push_back(1) + |> deque.push_back(2) + |> should_equal( + deque.new() + |> deque.push_front(2) + |> deque.push_front(1), + ) + + deque.new() + |> deque.push_back(1) + |> should_not_equal( + deque.new() + |> deque.push_front(2) + |> deque.push_front(1), + ) + + deque.new() + |> deque.push_back(2) + |> deque.push_back(1) + |> should_not_equal( + deque.new() + |> deque.push_front(2) + |> deque.push_front(1), + ) +} + +pub fn is_logically_equal_test() { + let both_even_or_odd = fn(a, b) { int.is_even(a) == int.is_even(b) } + + let should_equal = fn(a, b) { + a + |> deque.is_logically_equal(to: b, checking: both_even_or_odd) + |> should.be_true + } + + let should_not_equal = fn(a, b) { + a + |> deque.is_logically_equal(to: b, checking: both_even_or_odd) + |> should.be_false + } + + should_equal(deque.new(), deque.new()) + + deque.new() + |> deque.push_front(3) + |> should_equal( + deque.new() + |> deque.push_back(1), + ) + + deque.new() + |> deque.push_front(4) + |> should_equal( + deque.new() + |> deque.push_back(2), + ) + + deque.new() + |> deque.push_front(3) + |> should_equal( + deque.new() + |> deque.push_front(1), + ) + + deque.new() + |> deque.push_back(3) + |> deque.push_back(4) + |> should_equal( + deque.new() + |> deque.push_front(2) + |> deque.push_front(1), + ) + + deque.new() + |> deque.push_back(1) + |> should_not_equal( + deque.new() + |> deque.push_front(2) + |> deque.push_front(1), + ) + + deque.new() + |> deque.push_back(2) + |> deque.push_back(1) + |> should_not_equal( + deque.new() + |> deque.push_front(2) + |> deque.push_front(1), + ) + + deque.new() + |> deque.push_back(4) + |> deque.push_back(3) + |> should_not_equal( + deque.new() + |> deque.push_front(2) + |> deque.push_front(1), + ) +}