Compare commits

...

5 Commits

Author SHA1 Message Date
0fde06efe7 feat: More rework
Some checks failed
test / test (push) Has been cancelled
So the performance of this only surpasses list when you approach 100
items (and that is not including the need to traverse through the linked
list to find the insertion point) so this might be the last commit
2025-10-20 19:33:38 +01:00
f9aa688f5d fix: Reworked some api functions
Some checks failed
test / test (push) Has been cancelled
2025-10-19 22:51:33 +01:00
a5efb0ae8d test: Initial test work 2025-10-19 22:51:10 +01:00
f013d3320d rework: Remove asserts and reduced lets
Some checks failed
test / test (push) Has been cancelled
2025-10-17 23:41:21 +01:00
34d4bf1055 fix: Reordered to_list_loop parameter order 2025-10-17 23:29:32 +01:00
5 changed files with 369 additions and 92 deletions

View File

@@ -17,3 +17,5 @@ gleam_stdlib = ">= 0.44.0 and < 2.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
startest = ">= 0.7.0 and < 1.0.0"
glychee = ">= 1.1.2 and < 2.0.0"

View File

@@ -2,10 +2,36 @@
# You typically do not need to edit this file
packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "benchee", version = "1.4.0", build_tools = ["mix"], requirements = ["deep_merge", "statistex", "table"], otp_app = "benchee", source = "hex", outer_checksum = "299CD10DD8CE51C9EA3DDB74BB150F93D25E968F93E4C1FA31698A8E4FA5D715" },
{ name = "bigben", version = "1.0.1", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "bigben", source = "hex", outer_checksum = "190E489610A80D76C48BACC75EB8314BD184FF0220AB0F251ABE760B993B91BB" },
{ name = "birl", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "2AC7BA26F998E3DFADDB657148BD5DDFE966958AD4D6D6957DD0D22E5B56C400" },
{ name = "deep_merge", version = "1.0.0", build_tools = ["mix"], requirements = [], otp_app = "deep_merge", source = "hex", outer_checksum = "CE708E5F094B9CD4E8F2BE4F00D2F4250C4095BE93F8CD6D018C753894885430" },
{ name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" },
{ name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" },
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
{ name = "gleam_community_colour", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "E34DD2C896AC3792151EDA939DA435FF3B69922F33415ED3C4406C932FBE9634" },
{ name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
{ name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" },
{ name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" },
{ name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" },
{ name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" },
{ name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
{ name = "gleam_time", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" },
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
{ name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },
{ name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" },
{ name = "glychee", version = "1.1.2", build_tools = ["gleam"], requirements = ["benchee"], otp_app = "glychee", source = "hex", outer_checksum = "41784216C213F223095BB3FC3EDDB60CC537835B2340A868EA3931193F7F3824" },
{ name = "ranger", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_yielder"], otp_app = "ranger", source = "hex", outer_checksum = "C8988E8F8CDBD3E7F4D8F2E663EF76490390899C2B2885A6432E942495B3E854" },
{ name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" },
{ name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" },
{ name = "startest", version = "0.7.0", build_tools = ["gleam"], requirements = ["argv", "bigben", "birl", "exception", "gleam_community_ansi", "gleam_erlang", "gleam_javascript", "gleam_regexp", "gleam_stdlib", "glint", "simplifile", "tom"], otp_app = "startest", source = "hex", outer_checksum = "71B9CB82C4B8779A4BD54C7151DF7D0B0F778D0DDE805B782B44EFA7BA8F50DA" },
{ name = "statistex", version = "1.1.0", build_tools = ["mix"], requirements = [], otp_app = "statistex", source = "hex", outer_checksum = "F5950EA26AD43246BA2CCE54324AC394A4E7408FDCF98B8E230F503A0CBA9CF5" },
{ name = "tom", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "74D0C5A3761F7A7D06994755D4D5AD854122EF8E9F9F76A3E7547606D8C77091" },
]
[requirements]
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
glychee = { version = ">= 1.1.2 and < 2.0.0" }
startest = { version = ">= 0.7.0 and < 1.0.0" }

View File

@@ -11,7 +11,7 @@ pub opaque type Node(a) {
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)))
CyclicList(counter: Int, current: Int, mem: dict.Dict(Int, Node(a)))
}
pub fn main() {
@@ -35,27 +35,43 @@ pub fn main() {
// from_list([1, 2, 3, 4]) |> echo
let l1 = new() |> insert(1)
let l1 = new() |> insert(1) |> insert(2)
l1 |> echo
let l1t = take(l1, 5)
l1t |> echo
let l2 = insert(l1, 2)
l2 |> echo
let l2t = take(l2, 5)
l2t |> echo
get(l1) |> echo
// let l1t = take(l1, 5)
// l1t |> echo
// let l2 = insert(l1, 2) |> move_left
// l2 |> echo
// let l2t = take(l2, 5)
// l2t |> echo
let l3 = insert(l2, 3)
l3 |> echo
let l3t = take(l3, 7)
l3t |> echo
let l3t2 = take_rev(l3, 7)
l3t2 |> echo
// let l3 = insert(l2, 3)
// l3 |> echo
// to_list(l3) |> echo
// let l3t = take(l3, 7)
// l3t |> echo
// let l3t2 = take_rev(l3, 7)
// l3t2 |> echo
// let l1 = new_cyclic() |> echo
// let l2 = l1 |> insert(1) |> insert(2) |> insert(3)
// to_list(l2) |> echo
}
pub fn new() -> DLList(a) {
DLList(1, 0, dict.new())
}
pub fn new_cyclic() -> DLList(a) {
CyclicList(1, 0, dict.new())
}
fn clear(list: DLList(a)) -> DLList(a) {
case list {
DLList(_, _, _) -> DLList(1, 0, dict.new())
CyclicList(_, _, _) -> CyclicList(1, 0, dict.new())
}
}
fn get_curr_node(list: DLList(a)) -> Result(Node(a), Nil) {
dict.get(list.mem, list.current)
}
@@ -70,39 +86,71 @@ pub fn is_empty(list: DLList(a)) -> Bool {
pub fn insert(list: DLList(a), val: a) -> DLList(a) {
let ref = list.counter
let new_entry_ref = case list {
DLList(_, _, _) -> 0
CyclicList(_, _, _) -> ref
}
case is_empty(list) {
True -> {
let node = Node(val, ref, ref)
DLList(ref + 1, ref, dict.insert(list.mem, ref, node))
let node = Node(val, new_entry_ref, new_entry_ref)
update_record(
list,
Some(ref + 1),
Some(ref),
Some(dict.insert(list.mem, ref, node)),
)
}
False -> {
let assert Ok(curr_node) = dict.get(list.mem, list.current)
let curr_node_2 = Node(..curr_node, right: ref)
let assert Ok(curr_node) = get_curr_node(list)
let next = curr_node.right
let next_node = case next == list.current {
True -> Ok(curr_node_2)
True -> Ok(Node(..curr_node, right: ref))
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)
False ->
dict.insert(list.mem, list.current, Node(..curr_node, right: ref))
}
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)
dict.insert(new_mem, next, Node(..next_node, left: ref))
}
Error(_) -> new_mem
}
let new_node = Node(val, list.current, next)
let new_mem = dict.insert(new_mem, ref, new_node)
DLList(list.counter + 1, list.counter, new_mem)
update_record(
list,
Some(list.counter + 1),
Some(list.counter),
Some(dict.insert(new_mem, ref, Node(val, list.current, next))),
)
}
}
}
fn update_record(
list: DLList(a),
counter: Option(Int),
current: Option(Int),
mem: Option(dict.Dict(Int, Node(a))),
) -> DLList(a) {
case list {
DLList(_, _, _) ->
DLList(
counter: option.unwrap(counter, list.counter),
current: option.unwrap(current, list.current),
mem: option.unwrap(mem, list.mem),
)
CyclicList(_, _, _) ->
CyclicList(
counter: option.unwrap(counter, list.counter),
current: option.unwrap(current, list.current),
mem: option.unwrap(mem, list.mem),
)
}
}
pub fn delete(list: DLList(a)) -> DLList(a) {
let #(mem, curr_node) = remove_lookup(list, list.current)
case curr_node {
@@ -111,53 +159,61 @@ pub fn delete(list: DLList(a)) -> DLList(a) {
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, 0 -> clear(list)
0, n if n == list.current -> clear(list)
n, 0 if n == list.current -> clear(list)
n, m if n == list.current && m == list.current -> clear(list)
_, 0 -> {
DLList(
..list,
current: l,
mem: update_dict_entry(mem, l, fn(n) { Node(..n, right: r) }),
update_record(
list,
None,
Some(l),
Some(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) }),
update_record(
list,
None,
Some(l),
Some(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) }),
update_record(
list,
None,
Some(r),
Some(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) }),
update_record(
list,
None,
Some(r),
Some(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)
}),
update_record(
list,
None,
Some(r),
Some(
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_record(
list,
None,
Some(r),
Some(
update_dict_entry(mem, r, fn(n) { Node(..n, left: l) })
|> update_dict_entry(l, fn(n) { Node(..n, right: r) }),
),
)
}
}
@@ -174,8 +230,8 @@ fn do_move_right(list: DLList(a)) -> Result(DLList(a), Nil) {
Error(_) -> Error(Nil)
Ok(node) -> {
case node.right {
0 -> Ok(list)
r -> Ok(DLList(..list, current: r))
0 -> Error(Nil)
r -> Ok(update_record(list, None, Some(r), None))
}
}
}
@@ -191,8 +247,8 @@ fn do_move_left(list: DLList(a)) -> Result(DLList(a), Nil) {
Error(_) -> Error(Nil)
Ok(node) -> {
case node.left {
0 -> Ok(list)
l -> Ok(DLList(..list, current: l))
0 -> Error(Nil)
l -> Ok(update_record(list, None, Some(l), None))
}
}
}
@@ -202,17 +258,29 @@ pub fn from_list(list: List(a)) -> DLList(a) {
list.fold(list, new(), insert)
}
pub fn from_list_cyclic(list: List(a)) -> DLList(a) {
list.fold(list, new_cyclic(), insert)
}
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) {
list |> echo
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])
case get(list) {
Error(_) -> []
Ok(item) -> {
case move_right(list) {
r if r != list -> take_loop(r, n - 1, [item, ..acc])
_ -> list.reverse([item, ..acc])
}
}
}
}
}
}
@@ -226,42 +294,62 @@ fn take_rev_loop(list: DLList(a), n_times: Int, acc: List(a)) -> List(a) {
case n_times {
0 -> list.reverse(acc)
n -> {
let assert Ok(item) = get(list)
take_rev_loop(move_left(list), n - 1, [item, ..acc])
case get(list) {
Error(_) -> []
Ok(item) -> {
case move_left(list) {
l if l != list -> take_rev_loop(l, n - 1, [item, ..acc])
_ -> list.reverse([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))]
}
let list = case list {
DLList(_, _, _) -> DLList(..list, current: 1)
CyclicList(_, _, _) -> list
}
to_list_loop(Ok(list), list.current, []) |> list.reverse
}
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))]
fn to_list_loop(
list: Result(DLList(a), Nil),
start_ref: Int,
acc: List(a),
) -> List(a) {
case list |> echo {
Error(_) -> acc
Ok(DLList(_, _, _) as list) -> {
case get(list) {
Error(_) -> []
Ok(val) -> to_list_loop(do_move_right(list), start_ref, [val, ..acc])
}
}
Ok(CyclicList(_, _, _) as list) -> {
case get(list) {
Error(_) -> []
Ok(_) if start_ref == list.current && acc != [] -> acc
Ok(val) -> to_list_loop(do_move_right(list), start_ref, [val, ..acc])
}
}
}
}
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)),
),
)
case get_curr_node(list) {
Error(_) -> Error(Nil)
Ok(curr_node) ->
Ok(update_record(
list,
None,
None,
Some(dict.insert(list.mem, list.current, Node(..curr_node, val: value))),
))
}
}
fn update_dict_entry(

113
test/benchmark.gleam Normal file
View File

@@ -0,0 +1,113 @@
import dllist
import gleam/list
import glychee/benchmark
import glychee/configuration
@target(erlang)
pub fn main() {
configuration.initialize()
configuration.set_pair(configuration.Warmup, 2)
configuration.set_pair(configuration.Parallel, 2)
benchmark2()
}
@target(erlang)
fn benchmark() {
benchmark.run(
[
benchmark.Function(
label: "list append",
callable: fn(data: #(List(Int), Int)) {
fn() {
list.fold(data.0, [], fn(acc, i) { [i, ..acc] })
|> list.reverse
|> insert_at(data.1, 999)
Nil
}
},
),
benchmark.Function(
label: "dllist insert",
callable: fn(data: #(List(Int), Int)) {
fn() {
list.fold(data.0, dllist.new(), fn(acc, i) { dllist.insert(acc, i) })
|> move_left_n(data.1)
|> dllist.insert(999)
Nil
}
},
),
],
[
benchmark.Data(label: "6 Items", data: #(list.range(1, 6), 3)),
benchmark.Data(label: "10 Items", data: #(list.range(1, 10), 5)),
benchmark.Data(label: "100 Items", data: #(list.range(1, 100), 50)),
],
)
}
@target(erlang)
fn benchmark2() {
benchmark.run(
[
benchmark.Function(
label: "list append",
callable: fn(data: #(List(Int), dllist.DLList(Int), Int)) {
fn() {
let _ = insert_at(data.0, data.2, 999)
Nil
}
},
),
benchmark.Function(
label: "dllist insert",
callable: fn(data: #(List(Int), dllist.DLList(Int), Int)) {
fn() {
let _ = dllist.insert(data.1, 999)
Nil
}
},
),
],
[
benchmark.Data(label: "6 Items", data: #(
list.range(1, 6),
dllist.from_list(list.range(1, 6)) |> move_left_n(3),
3,
)),
benchmark.Data(label: "10 Items", data: #(
list.range(1, 10),
dllist.from_list(list.range(1, 10)) |> move_left_n(5),
5,
)),
benchmark.Data(label: "100 Items", data: #(
list.range(1, 100),
dllist.from_list(list.range(1, 100)) |> move_left_n(50),
50,
)),
benchmark.Data(label: "1000 Items", data: #(
list.range(1, 1000),
dllist.from_list(list.range(1, 1000)) |> move_left_n(500),
500,
)),
benchmark.Data(label: "10000 Items", data: #(
list.range(1, 10_000),
dllist.from_list(list.range(1, 10_000)) |> move_left_n(5000),
5000,
)),
],
)
}
fn insert_at(list: List(a), pos: Int, value: a) -> List(a) {
let #(l1, l2) = list.split(list, pos)
list.append(l1, [value, ..l2])
}
fn move_left_n(list: dllist.DLList(a), n: Int) -> dllist.DLList(a) {
case n {
0 -> list
n -> move_left_n(dllist.move_left(list), n - 1)
}
}

View File

@@ -1,13 +1,61 @@
import gleeunit
import dllist
import gleam/dict
import startest
pub fn main() -> Nil {
gleeunit.main()
startest.run(startest.default_config())
}
// gleeunit test functions end in `_test`
pub fn hello_world_test() {
let name = "Joe"
let greeting = "Hello, " <> name <> "!"
assert greeting == "Hello, Joe!"
pub fn new_tests() {
startest.describe("new instance", [
startest.it("terminated list", fn() {
assert dllist.new() == dllist.DLList(1, 0, dict.from_list([]))
}),
startest.it("cyclic list", fn() {
assert dllist.new_cyclic() == dllist.CyclicList(1, 0, dict.from_list([]))
}),
])
}
pub fn is_empty_tests() {
startest.describe("is_emtpy", [
startest.it("terminated list", fn() {
assert dllist.is_empty(dllist.new())
assert !dllist.is_empty(dllist.new() |> dllist.insert(1))
}),
startest.it("cyclic list", fn() {
assert dllist.is_empty(dllist.new_cyclic())
assert !dllist.is_empty(dllist.new_cyclic() |> dllist.insert(1))
}),
])
}
pub fn from_list_tests() {
startest.describe("from_list", [
startest.it("terminated list", fn() {
assert dllist.is_empty(dllist.from_list([]))
assert !dllist.is_empty(dllist.from_list([1]))
assert dllist.from_list([1]) |> dllist.get == Ok(1)
assert dllist.from_list([1, 2, 3, 4]) |> dllist.get == Ok(4)
assert dllist.from_list([1]) |> dllist.to_list == [1]
assert dllist.from_list([1, 2, 3, 4]) |> dllist.to_list == [1, 2, 3, 4]
}),
startest.it("cyclic list", fn() {
assert dllist.is_empty(dllist.from_list_cyclic([]))
assert !dllist.is_empty(dllist.from_list_cyclic([1]))
assert dllist.from_list_cyclic([1]) |> dllist.get == Ok(1)
assert dllist.from_list_cyclic([1, 2, 3, 4]) |> dllist.get == Ok(4)
assert dllist.from_list_cyclic([1]) |> dllist.to_list == [1]
assert dllist.from_list_cyclic([1, 2, 3, 4]) |> dllist.to_list
== [4, 1, 2, 3]
}),
])
}