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
This commit is contained in:
2025-10-20 19:33:38 +01:00
parent f9aa688f5d
commit 0fde06efe7
5 changed files with 251 additions and 68 deletions

View File

@@ -18,3 +18,4 @@ 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

@@ -3,8 +3,10 @@
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" },
@@ -19,14 +21,17 @@ packages = [
{ 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

@@ -35,22 +35,26 @@ 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) |> move_left
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
to_list(l3) |> 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) {
@@ -89,7 +93,12 @@ pub fn insert(list: DLList(a), val: a) -> DLList(a) {
case is_empty(list) {
True -> {
let node = Node(val, new_entry_ref, new_entry_ref)
DLList(ref + 1, ref, dict.insert(list.mem, ref, node))
update_record(
list,
Some(ref + 1),
Some(ref),
Some(dict.insert(list.mem, ref, node)),
)
}
False -> {
let assert Ok(curr_node) = get_curr_node(list)
@@ -110,11 +119,11 @@ pub fn insert(list: DLList(a), val: a) -> DLList(a) {
}
Error(_) -> new_mem
}
DLList(
counter: list.counter + 1,
current: list.counter,
mem: dict.insert(new_mem, ref, Node(val, list.current, next)),
update_record(
list,
Some(list.counter + 1),
Some(list.counter),
Some(dict.insert(new_mem, ref, Node(val, list.current, next))),
)
}
}
@@ -122,12 +131,23 @@ pub fn insert(list: DLList(a), val: a) -> DLList(a) {
fn update_record(
list: DLList(a),
current: Int,
mem: dict.Dict(Int, Node(a)),
counter: Option(Int),
current: Option(Int),
mem: Option(dict.Dict(Int, Node(a))),
) -> DLList(a) {
case list {
DLList(_, _, _) -> DLList(..list, current:, mem:)
CyclicList(_, _, _) -> CyclicList(..list, current:, mem:)
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),
)
}
}
@@ -146,44 +166,54 @@ pub fn delete(list: DLList(a)) -> DLList(a) {
_, 0 -> {
update_record(
list,
l,
update_dict_entry(mem, l, fn(n) { Node(..n, right: r) }),
None,
Some(l),
Some(update_dict_entry(mem, l, fn(n) { Node(..n, right: r) })),
)
}
_, r if r == list.current -> {
update_record(
list,
l,
update_dict_entry(mem, l, fn(n) { Node(..n, right: l) }),
None,
Some(l),
Some(update_dict_entry(mem, l, fn(n) { Node(..n, right: l) })),
)
}
0, _ -> {
update_record(
list,
r,
update_dict_entry(mem, r, fn(n) { Node(..n, left: l) }),
None,
Some(r),
Some(update_dict_entry(mem, r, fn(n) { Node(..n, left: l) })),
)
}
l, _ if l == list.current -> {
update_record(
list,
r,
update_dict_entry(mem, r, fn(n) { Node(..n, left: r) }),
None,
Some(r),
Some(update_dict_entry(mem, r, fn(n) { Node(..n, left: r) })),
)
}
l, r if l == r -> {
update_record(
list,
r,
None,
Some(r),
Some(
update_dict_entry(mem, r, fn(n) { Node(..n, left: r, right: r) }),
),
)
}
_, _ -> {
update_record(
list,
r,
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) }),
),
)
}
}
@@ -199,10 +229,9 @@ fn do_move_right(list: DLList(a)) -> Result(DLList(a), Nil) {
case get_curr_node(list) {
Error(_) -> Error(Nil)
Ok(node) -> {
case node.right, list {
0, _ -> Error(Nil)
r, DLList(_, _, _) -> Ok(DLList(..list, current: r))
r, CyclicList(_, _, _) -> Ok(CyclicList(..list, current: r))
case node.right {
0 -> Error(Nil)
r -> Ok(update_record(list, None, Some(r), None))
}
}
}
@@ -217,10 +246,9 @@ fn do_move_left(list: DLList(a)) -> Result(DLList(a), Nil) {
case get_curr_node(list) {
Error(_) -> Error(Nil)
Ok(node) -> {
case node.left, list {
0, _ -> Error(Nil)
l, DLList(_, _, _) -> Ok(DLList(..list, current: l))
l, CyclicList(_, _, _) -> Ok(CyclicList(..list, current: l))
case node.left {
0 -> Error(Nil)
l -> Ok(update_record(list, None, Some(l), None))
}
}
}
@@ -230,6 +258,10 @@ 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, [])
}
@@ -280,7 +312,7 @@ pub fn to_list(list: DLList(a)) -> List(a) {
DLList(_, _, _) -> DLList(..list, current: 1)
CyclicList(_, _, _) -> list
}
to_list_loop(Ok(list), list.current, [])
to_list_loop(Ok(list), list.current, []) |> list.reverse
}
fn to_list_loop(
@@ -288,43 +320,35 @@ fn to_list_loop(
start_ref: Int,
acc: List(a),
) -> List(a) {
case list {
Error(_) -> list.reverse(acc)
Ok(list) -> {
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))
case get_curr_node(list), list {
Error(_), _ -> Error(Nil)
Ok(curr_node), DLList(_, _, _) ->
Ok(
DLList(
..list,
mem: dict.insert(
list.mem,
list.current,
Node(..curr_node, val: value),
),
),
)
Ok(curr_node), CyclicList(_, _, _) ->
Ok(
CyclicList(
..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))),
))
}
}

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,10 +1,22 @@
import dllist
import gleam/dict
import startest
pub fn main() -> Nil {
startest.run(startest.default_config())
}
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() {
@@ -19,3 +31,31 @@ pub fn is_empty_tests() {
}),
])
}
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]
}),
])
}