This commit is contained in:
23
.github/workflows/test.yml
vendored
Normal file
23
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
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.12.0"
|
||||
rebar3-version: "3"
|
||||
# elixir-version: "1"
|
||||
- run: gleam deps download
|
||||
- run: gleam test
|
||||
- run: gleam format --check src test
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.beam
|
||||
*.ez
|
||||
/build
|
||||
erl_crash.dump
|
||||
26
README.md
26
README.md
@@ -1,4 +1,26 @@
|
||||
# dllist
|
||||
|
||||
Gleam implementation of a doubly-linked list.
|
||||
Allows for a cyclic or terminated lists
|
||||
Gleam implementation of a doubly-linked list.
|
||||
Allows for a cyclic or terminated lists
|
||||
[](https://hex.pm/packages/dllist)
|
||||
[](https://hexdocs.pm/dllist/)
|
||||
|
||||
```sh
|
||||
gleam add dllist@1
|
||||
```
|
||||
```gleam
|
||||
import dllist
|
||||
|
||||
pub fn main() -> Nil {
|
||||
// TODO: An example of the project in use
|
||||
}
|
||||
```
|
||||
|
||||
Further documentation can be found at <https://hexdocs.pm/dllist>.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
gleam run # Run the project
|
||||
gleam test # Run the tests
|
||||
```
|
||||
|
||||
19
gleam.toml
Normal file
19
gleam.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
name = "dllist"
|
||||
version = "1.0.0"
|
||||
|
||||
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||
# your project to the Hex package manager.
|
||||
#
|
||||
# description = ""
|
||||
# licences = ["Apache-2.0"]
|
||||
# repository = { type = "github", user = "", repo = "" }
|
||||
# links = [{ title = "Website", href = "" }]
|
||||
#
|
||||
# For a full reference of all the available options, you can have a look at
|
||||
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||
11
manifest.toml
Normal file
11
manifest.toml
Normal file
@@ -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.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
|
||||
{ name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
270
src/dllist.gleam
Normal file
270
src/dllist.gleam
Normal file
@@ -0,0 +1,270 @@
|
||||
import gleam/bool
|
||||
import gleam/dict
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/result
|
||||
|
||||
/// https://hackage.haskell.org/package/liboleg-2009.9.1/docs/src/Data-FDList.html#empty
|
||||
pub opaque type Node(a) {
|
||||
Node(val: a, left: Int, right: Int)
|
||||
}
|
||||
|
||||
pub type DLList(a) {
|
||||
DLList(counter: Int, current: Int, mem: dict.Dict(Int, Node(a)))
|
||||
// CyclicList(counter: Int, current: Int, mem: dict.Dict(Int, Node(a)))
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let l = new()
|
||||
let assert Ok(l2) = insert_right(l, 123) |> echo
|
||||
let assert Ok(l3) = insert_right(l2, 456) |> echo
|
||||
let assert Ok(l4) = insert_right(l3, 789)
|
||||
let assert Ok(l5) = insert_right(l4, 0)
|
||||
|
||||
get(l5) |> echo
|
||||
l5 |> echo
|
||||
l5 |> to_list |> echo
|
||||
let l6 = delete(l5)
|
||||
l6 |> echo
|
||||
let l7 = delete(l6)
|
||||
l7 |> echo
|
||||
let l8 = delete(l7)
|
||||
l8 |> echo
|
||||
let l9 = delete(l8)
|
||||
l9 |> echo
|
||||
|
||||
from_list([1, 2, 3, 4]) |> echo
|
||||
|
||||
let assert Ok(l1) = new() |> insert_right(1)
|
||||
l1 |> echo
|
||||
let l1t = take(l1, 5)
|
||||
l1t |> echo
|
||||
let assert Ok(l2) = insert_right(l1, 2)
|
||||
l2 |> echo
|
||||
let l2t = take(l2, 5)
|
||||
l2t |> echo
|
||||
}
|
||||
|
||||
pub fn new() -> DLList(a) {
|
||||
DLList(1, 0, dict.new())
|
||||
}
|
||||
|
||||
fn get_curr_node(list: DLList(a)) -> Result(Node(a), Nil) {
|
||||
dict.get(list.mem, list.current)
|
||||
}
|
||||
|
||||
pub fn get(list: DLList(a)) -> Result(a, Nil) {
|
||||
get_curr_node(list) |> result.map(fn(n) { n.val })
|
||||
}
|
||||
|
||||
pub fn is_empty(list: DLList(a)) -> Bool {
|
||||
dict.is_empty(list.mem)
|
||||
}
|
||||
|
||||
pub fn insert_right(list: DLList(a), val: a) -> Result(DLList(a), Nil) {
|
||||
let ref = list.counter
|
||||
case is_empty(list) {
|
||||
True -> {
|
||||
let node = Node(val, ref, ref)
|
||||
Ok(DLList(ref + 1, ref, dict.insert(list.mem, ref, node)))
|
||||
}
|
||||
False -> {
|
||||
use curr_node <- result.try(dict.get(list.mem, list.current))
|
||||
let curr_node_2 = Node(..curr_node, right: ref)
|
||||
let next = curr_node.right
|
||||
|
||||
let next_node = case next == list.current {
|
||||
True -> Ok(curr_node_2)
|
||||
False -> dict.get(list.mem, next)
|
||||
}
|
||||
let new_mem = case next == list.current {
|
||||
True -> list.mem
|
||||
False -> dict.insert(list.mem, list.current, curr_node_2)
|
||||
}
|
||||
let new_mem = case next_node {
|
||||
Ok(next_node) -> {
|
||||
let next_node_2 = Node(..next_node, left: ref)
|
||||
dict.insert(new_mem, next, next_node_2)
|
||||
}
|
||||
Error(_) -> new_mem
|
||||
}
|
||||
let new_node = Node(val, list.current, next)
|
||||
|
||||
let new_mem = dict.insert(new_mem, ref, new_node)
|
||||
Ok(DLList(list.counter + 1, list.counter, new_mem))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(list: DLList(a)) -> DLList(a) {
|
||||
let #(mem, curr_node) = remove_lookup(list, list.current)
|
||||
case curr_node {
|
||||
None -> list
|
||||
Some(curr_node) -> {
|
||||
let l = curr_node.left
|
||||
let r = curr_node.right
|
||||
case l, r {
|
||||
0, 0 -> new()
|
||||
0, n if n == list.current -> new()
|
||||
n, 0 if n == list.current -> new()
|
||||
n, m if n == list.current && m == list.current -> new()
|
||||
_, 0 -> {
|
||||
DLList(
|
||||
..list,
|
||||
current: l,
|
||||
mem: update_dict_entry(mem, l, fn(n) { Node(..n, right: r) }),
|
||||
)
|
||||
}
|
||||
_, r if r == list.current -> {
|
||||
DLList(
|
||||
..list,
|
||||
current: l,
|
||||
mem: update_dict_entry(mem, l, fn(n) { Node(..n, right: l) }),
|
||||
)
|
||||
}
|
||||
0, _ -> {
|
||||
DLList(
|
||||
..list,
|
||||
current: r,
|
||||
mem: update_dict_entry(mem, r, fn(n) { Node(..n, left: l) }),
|
||||
)
|
||||
}
|
||||
l, _ if l == list.current -> {
|
||||
DLList(
|
||||
..list,
|
||||
current: r,
|
||||
mem: update_dict_entry(mem, r, fn(n) { Node(..n, left: r) }),
|
||||
)
|
||||
}
|
||||
l, r if l == r -> {
|
||||
DLList(
|
||||
..list,
|
||||
current: r,
|
||||
mem: update_dict_entry(mem, r, fn(n) {
|
||||
Node(..n, left: r, right: r)
|
||||
}),
|
||||
)
|
||||
}
|
||||
_, _ -> {
|
||||
DLList(
|
||||
..list,
|
||||
current: r,
|
||||
mem: update_dict_entry(mem, r, fn(n) { Node(..n, left: l) })
|
||||
|> update_dict_entry(l, fn(n) { Node(..n, right: r) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_right(list: DLList(a)) -> DLList(a) {
|
||||
do_move_right(list) |> result.unwrap(list)
|
||||
}
|
||||
|
||||
fn do_move_right(list: DLList(a)) -> Result(DLList(a), Nil) {
|
||||
case get_curr_node(list) {
|
||||
Error(_) -> Error(Nil)
|
||||
Ok(node) -> {
|
||||
case node.right {
|
||||
0 -> Ok(list)
|
||||
r -> Ok(DLList(..list, current: r))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_left(list: DLList(a)) -> DLList(a) {
|
||||
do_move_left(list)
|
||||
|> result.unwrap(list)
|
||||
}
|
||||
|
||||
fn do_move_left(list: DLList(a)) -> Result(DLList(a), Nil) {
|
||||
case get_curr_node(list) {
|
||||
Error(_) -> Error(Nil)
|
||||
Ok(node) -> {
|
||||
case node.left {
|
||||
0 -> Ok(list)
|
||||
l -> Ok(DLList(..list, current: l))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_list(list: List(a)) -> Result(DLList(a), Nil) {
|
||||
list.try_fold(list, new(), insert_right)
|
||||
}
|
||||
|
||||
pub fn take(list: DLList(a), n_times: Int) -> List(a) {
|
||||
take_loop(list, n_times, [])
|
||||
}
|
||||
|
||||
fn take_loop(list: DLList(a), n_times: Int, acc: List(a)) -> List(a) {
|
||||
use <- bool.guard(when: is_empty(list), return: [])
|
||||
case n_times {
|
||||
0 -> list.reverse(acc)
|
||||
n -> {
|
||||
let assert Ok(item) = get(list)
|
||||
take_loop(move_right(list), n - 1, [item, ..acc])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_list(list: DLList(a)) -> List(a) {
|
||||
case is_empty(list) {
|
||||
True -> []
|
||||
False -> {
|
||||
let assert Ok(a) = get(list)
|
||||
[a, ..to_list_loop(list.current, do_move_right(list))]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_list_loop(ref: Int, list: Result(DLList(a), Nil)) -> List(a) {
|
||||
case list {
|
||||
Error(_) -> []
|
||||
Ok(DLList(_, current, _)) if current == ref -> []
|
||||
Ok(list) -> {
|
||||
let assert Ok(a) = get(list)
|
||||
[a, ..to_list_loop(ref, do_move_right(list))]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(list: DLList(a), value: a) -> Result(DLList(a), Nil) {
|
||||
use <- bool.guard(when: is_empty(list), return: Error(Nil))
|
||||
let assert Ok(curr_node) = get_curr_node(list)
|
||||
Ok(
|
||||
DLList(
|
||||
..list,
|
||||
mem: dict.insert(list.mem, list.current, Node(..curr_node, val: value)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn update_dict_entry(
|
||||
list: dict.Dict(Int, Node(a)),
|
||||
ref: Int,
|
||||
upd: fn(Node(a)) -> Node(a),
|
||||
) -> dict.Dict(Int, Node(a)) {
|
||||
case dict.get(list, ref) {
|
||||
Ok(n) -> {
|
||||
dict.insert(list, ref, upd(n))
|
||||
}
|
||||
Error(_) -> list
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_lookup(
|
||||
list: DLList(a),
|
||||
id: Int,
|
||||
) -> #(dict.Dict(Int, Node(a)), Option(Node(a))) {
|
||||
case dict.get(list.mem, id) {
|
||||
Ok(Node(_, _, _) as n) -> {
|
||||
#(dict.delete(list.mem, id), Some(n))
|
||||
}
|
||||
Error(_) -> {
|
||||
#(list.mem, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
13
test/dllist_test.gleam
Normal file
13
test/dllist_test.gleam
Normal file
@@ -0,0 +1,13 @@
|
||||
import gleeunit
|
||||
|
||||
pub fn main() -> Nil {
|
||||
gleeunit.main()
|
||||
}
|
||||
|
||||
// gleeunit test functions end in `_test`
|
||||
pub fn hello_world_test() {
|
||||
let name = "Joe"
|
||||
let greeting = "Hello, " <> name <> "!"
|
||||
|
||||
assert greeting == "Hello, Joe!"
|
||||
}
|
||||
Reference in New Issue
Block a user