From f1fcf0988d9cacf1441f31a8856dbff1916bd568 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 14 May 2025 16:24:51 +0300 Subject: [PATCH 001/123] Added concat function (not tested). --- src/List.mo | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/List.mo b/src/List.mo index d5662bacf..2700b86ac 100644 --- a/src/List.mo +++ b/src/List.mo @@ -22,6 +22,7 @@ import Order "Order"; import Option "Option"; import VarArray "VarArray"; import Types "Types"; +import Runtime "Runtime"; module { /// `List` provides a mutable list of elements of type `T`. @@ -98,6 +99,8 @@ module { } }; + // func repeatInternal(initValue : ) + /// Converts a mutable `List` to a purely functional `PureList`. /// /// Example: @@ -1032,12 +1035,19 @@ module { next : () -> ?T; unsafe_next : () -> T; unsafe_next_i : Nat -> T + } = values_from_(0, list); + + private func values_from_(start : Nat, list : List) : { + next : () -> ?T; + unsafe_next : () -> T; + unsafe_next_i : Nat -> T } = object { let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var db_size = 0; - var db : [var ?T] = [var]; + let (block, element) = locate(start); + var blockIndex = block; + var elementIndex = element; + var db : [var ?T] = list.blocks[block]; + var db_size = db.size(); public func next() : ?T { if (elementIndex == db_size) { @@ -1797,5 +1807,34 @@ module { /// Space: `O(1)` public func isEmpty(list : List) : Bool { list.blockIndex == 1 and list.elementIndex == 0 + }; + + public type Slice = { + list : List; + start : Nat; + end : Nat + }; + + public func concat(slices : [Slice]) : List { + for (slice in slices.vals()) { + let { list; start; end } = slice; + let sz = size(list); + let ok = start <= end and end <= sz; + if (not ok) { + Runtime.trap("Invalid slice in concat") + } + }; + + var list = empty(); + for (slice in slices.vals()) { + let { list; start; end } = slice; + let values = values_from_(start, list); + var i = start; + while (i < end) { + add(list, values.unsafe_next()) + } + }; + + list } } From 98189e26fc80e8723d7f40b39a0b2afa5a6f44bc Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 14 May 2025 16:37:57 +0300 Subject: [PATCH 002/123] Move slice type into types. --- src/List.mo | 10 +++------- src/Types.mo | 6 ++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/List.mo b/src/List.mo index 2700b86ac..848f74a01 100644 --- a/src/List.mo +++ b/src/List.mo @@ -32,6 +32,8 @@ module { /// has `O(size)` memory waste. public type List = Types.List; + public type ListSlice = Types.ListSlice; + let INTERNAL_ERROR = "List: internal error"; /// Creates a new empty List for elements of type T. @@ -1809,13 +1811,7 @@ module { list.blockIndex == 1 and list.elementIndex == 0 }; - public type Slice = { - list : List; - start : Nat; - end : Nat - }; - - public func concat(slices : [Slice]) : List { + public func concat(slices : [ListSlice]) : List { for (slice in slices.vals()) { let { list; start; end } = slice; let sz = size(list); diff --git a/src/Types.mo b/src/Types.mo index 52c80e7ee..69ab4fcca 100644 --- a/src/Types.mo +++ b/src/Types.mo @@ -47,6 +47,12 @@ module { var elementIndex : Nat }; + public type ListSlice = { + list : List; + start : Nat; + end : Nat + }; + public module Queue { public type Queue = { var front : ?Node; From c85d7767639727c8004704e3dd331561380f54d2 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 14 May 2025 17:38:44 +0300 Subject: [PATCH 003/123] Added tests, fixed bugs. --- src/List.mo | 19 ++++++++++++------- test/List.test.mo | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/List.mo b/src/List.mo index 848f74a01..cc38e31ca 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1045,10 +1045,14 @@ module { unsafe_next_i : Nat -> T } = object { let blocks = list.blocks.size(); - let (block, element) = locate(start); - var blockIndex = block; - var elementIndex = element; - var db : [var ?T] = list.blocks[block]; + var blockIndex = 0; + var elementIndex = 0; + if (start != 0) { + let (block, element) = if (start == 0) (0, 0) else locate(start - 1); + blockIndex := block; + elementIndex := element + 1 + }; + var db : [var ?T] = list.blocks[blockIndex]; var db_size = db.size(); public func next() : ?T { @@ -1821,16 +1825,17 @@ module { } }; - var list = empty(); + var result = empty(); for (slice in slices.vals()) { let { list; start; end } = slice; let values = values_from_(start, list); var i = start; while (i < end) { - add(list, values.unsafe_next()) + add(result, values.unsafe_next()); + i += 1 } }; - list + result } } diff --git a/test/List.test.mo b/test/List.test.mo index 599253199..3b367bb6e 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1324,4 +1324,45 @@ Test.suite( } ) } -) +); + +run( + suite( + "concat", + [ + test( + "concat with valid slices", + do { + let list1 = List.fromArray([1, 2, 3]); + let list2 = List.fromArray([4, 5, 6]); + let slice1 = { list = list1; start = 0; end = 2 }; // [1, 2] + let slice2 = { list = list2; start = 1; end = 3 }; // [5, 6] + let result = List.concat([slice1, slice2]); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [1, 2, 5, 6])) + ), + test( + "concat with empty slices", + do { + let list1 = List.fromArray([1, 2, 3]); + let slice1 = { list = list1; start = 1; end = 1 }; // [] + let result = List.concat([slice1]); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "concat with overlapping slices", + do { + let list1 = List.fromArray([1, 2, 3, 4]); + let slice1 = { list = list1; start = 0; end = 2 }; // [1, 2] + let slice2 = { list = list1; start = 1; end = 4 }; // [2, 3, 4] + let result = List.concat([slice1, slice2]); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [1, 2, 2, 3, 4])) + ) + ] + ) +); \ No newline at end of file From b8841dd478bbbfef89644a1bde86af3dd71724fb Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 15 May 2025 15:45:55 +0300 Subject: [PATCH 004/123] Make concat allocate the result beforehand. Remove ListSlice type. --- src/List.mo | 105 ++++++++++++++++++++++++++++++---------------- src/Types.mo | 6 --- test/List.test.mo | 101 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 165 insertions(+), 47 deletions(-) diff --git a/src/List.mo b/src/List.mo index cc38e31ca..c472cd52d 100644 --- a/src/List.mo +++ b/src/List.mo @@ -32,8 +32,6 @@ module { /// has `O(size)` memory waste. public type List = Types.List; - public type ListSlice = Types.ListSlice; - let INTERNAL_ERROR = "List: internal error"; /// Creates a new empty List for elements of type T. @@ -63,32 +61,21 @@ module { /// Space: `O(1)` public func singleton(element : T) : List = repeat(element, 1); - /// Creates a new List with `size` copies of the initial value. - /// - /// Example: - /// ```motoko include=import - /// let list = List.repeat(2, 4); - /// assert List.toArray(list) == [2, 2, 2, 2]; - /// ``` - /// - /// Runtime: `O(size)` - /// - /// Space: `O(size)` - public func repeat(initValue : T, size : Nat) : List { + private func repeatInternal(initValue : ?T, size : Nat) : List { let (blockIndex, elementIndex) = locate(size); let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let data_blocks = VarArray.repeat<[var ?T]>([var], blocks); var i = 1; while (i < blockIndex) { - data_blocks[i] := VarArray.repeat(?initValue, data_block_size(i)); + data_blocks[i] := VarArray.repeat(initValue, data_block_size(i)); i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { let block = VarArray.repeat(null, data_block_size(i)); var j = 0; while (j < elementIndex) { - block[j] := ?initValue; + block[j] := initValue; j += 1 }; data_blocks[i] := block @@ -101,7 +88,18 @@ module { } }; - // func repeatInternal(initValue : ) + /// Creates a new List with `size` copies of the initial value. + /// + /// Example: + /// ```motoko include=import + /// let list = List.repeat(2, 4); + /// assert List.toArray(list) == [2, 2, 2, 2]; + /// ``` + /// + /// Runtime: `O(size)` + /// + /// Space: `O(size)` + public func repeat(initValue : T, size : Nat) : List = repeatInternal(?initValue, size); /// Converts a mutable `List` to a purely functional `PureList`. /// @@ -137,15 +135,7 @@ module { list }; - /// Add to list `count` copies of the initial value. - /// - /// ```motoko include=import - /// let list = List.repeat(2, 4); // [2, 2, 2, 2] - /// List.addRepeat(list, 2, 1); // [2, 2, 2, 2, 1, 1] - /// ``` - /// - /// Runtime: `O(count)` - public func addRepeat(list : List, initValue : T, count : Nat) { + private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { let (blockIndex, elementIndex) = locate(size(list) + count); let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); @@ -164,7 +154,7 @@ module { while (cnt > 0) { let db_size = data_block_size(list.blockIndex); if (list.elementIndex == 0 and db_size <= cnt) { - list.blocks[list.blockIndex] := VarArray.repeat(?initValue, db_size); + list.blocks[list.blockIndex] := VarArray.repeat(initValue, db_size); cnt -= db_size; list.blockIndex += 1 } else { @@ -177,7 +167,7 @@ module { let block = list.blocks[list.blockIndex]; var i = from; while (i < to) { - block[i] := ?initValue; + block[i] := initValue; i += 1 }; @@ -191,6 +181,16 @@ module { } }; + /// Add to list `count` copies of the initial value. + /// + /// ```motoko include=import + /// let list = List.repeat(2, 4); // [2, 2, 2, 2] + /// List.addRepeat(list, 2, 1); // [2, 2, 2, 2, 1, 1] + /// ``` + /// + /// Runtime: `O(count)` + public func addRepeat(list : List, initValue : T, count : Nat) = addRepeatInternal(list, ?initValue, count); + /// Resets the list to size 0, de-referencing all elements. /// /// Example: @@ -1036,13 +1036,15 @@ module { private func values_(list : List) : { next : () -> ?T; unsafe_next : () -> T; - unsafe_next_i : Nat -> T + unsafe_next_i : Nat -> T; + next_set : T -> () } = values_from_(0, list); private func values_from_(start : Nat, list : List) : { next : () -> ?T; unsafe_next : () -> T; - unsafe_next_i : Nat -> T + unsafe_next_i : Nat -> T; + next_set : T -> () } = object { let blocks = list.blocks.size(); var blockIndex = 0; @@ -1116,6 +1118,19 @@ module { }; case (_) Prim.trap(INTERNAL_ERROR) } + }; + + public func next_set(value : T) { + if (elementIndex == db_size) { + blockIndex += 1; + if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); + db := list.blocks[blockIndex]; + db_size := db.size(); + if (db_size == 0) Prim.trap(INTERNAL_ERROR); + elementIndex := 0 + }; + db[elementIndex] := ?value; + elementIndex += 1 } }; @@ -1815,23 +1830,41 @@ module { list.blockIndex == 1 and list.elementIndex == 0 }; - public func concat(slices : [ListSlice]) : List { + /// Concatenates the provided slices into a new list. + /// Each slice is a tuple of a list, a starting index (inclusive), and an ending index (exclusive). + /// + /// Example: + /// ```motoko include=import + /// let list1 = List.fromArray([1, 2, 3]); + /// let list2 = List.fromArray([4, 5, 6]); + /// let result = List.concat([(list1, 0, 2), (list2, 1, 3)]); + /// assert Iter.toArray(List.values(result)) == [1, 2, 5, 6]; + /// ``` + /// + /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all slices. + /// + /// Space: `O(sum_size)` + public func concat(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List { + var length = 0; for (slice in slices.vals()) { - let { list; start; end } = slice; + let (list, start, end) = slice; let sz = size(list); let ok = start <= end and end <= sz; if (not ok) { Runtime.trap("Invalid slice in concat") - } + }; + length += end - start }; - var result = empty(); + var result = repeatInternal(null, length); + var resultIter = values_(result); for (slice in slices.vals()) { - let { list; start; end } = slice; + let (list, start, end) = slice; let values = values_from_(start, list); var i = start; while (i < end) { - add(result, values.unsafe_next()); + let copiedValue = values.unsafe_next(); + resultIter.next_set(copiedValue); i += 1 } }; diff --git a/src/Types.mo b/src/Types.mo index 69ab4fcca..52c80e7ee 100644 --- a/src/Types.mo +++ b/src/Types.mo @@ -47,12 +47,6 @@ module { var elementIndex : Nat }; - public type ListSlice = { - list : List; - start : Nat; - end : Nat - }; - public module Queue { public type Queue = { var front : ?Node; diff --git a/test/List.test.mo b/test/List.test.mo index 3b367bb6e..b809d84dc 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1335,8 +1335,8 @@ run( do { let list1 = List.fromArray([1, 2, 3]); let list2 = List.fromArray([4, 5, 6]); - let slice1 = { list = list1; start = 0; end = 2 }; // [1, 2] - let slice2 = { list = list2; start = 1; end = 3 }; // [5, 6] + let slice1 = (list1, 0, 2); // [1, 2] + let slice2 = (list2, 1, 3); // [5, 6] let result = List.concat([slice1, slice2]); List.toArray(result) }, @@ -1346,7 +1346,7 @@ run( "concat with empty slices", do { let list1 = List.fromArray([1, 2, 3]); - let slice1 = { list = list1; start = 1; end = 1 }; // [] + let slice1 = (list1, 1, 1); // [] let result = List.concat([slice1]); List.toArray(result) }, @@ -1356,8 +1356,8 @@ run( "concat with overlapping slices", do { let list1 = List.fromArray([1, 2, 3, 4]); - let slice1 = { list = list1; start = 0; end = 2 }; // [1, 2] - let slice2 = { list = list1; start = 1; end = 4 }; // [2, 3, 4] + let slice1 = (list1, 0, 2); // [1, 2] + let slice2 = (list1, 1, 4); // [2, 3, 4] let result = List.concat([slice1, slice2]); List.toArray(result) }, @@ -1365,4 +1365,95 @@ run( ) ] ) +); + +run( + suite( + "concat (complicated cases)", + [ + test( + "concat with many slices from different lists", + do { + let l1 = List.fromArray([10, 11, 12, 13]); + let l2 = List.fromArray([20, 21]); + let l3 = List.fromArray([30, 31, 32]); + let slices = [ + (l1, 1, 3), // [11, 12] + (l2, 0, 2), // [20, 21] + (l3, 1, 3) // [31, 32] + ]; + let result = List.concat(slices); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [11, 12, 20, 21, 31, 32])) + ), + test( + "concat with all slices empty", + do { + let l1 = List.fromArray([1, 2]); + let l2 = List.fromArray([3, 4]); + let slices = [ + (l1, 0, 0), // [] + (l2, 1, 1) // [] + ]; + let result = List.concat(slices); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "concat with single element slices", + do { + let l1 = List.fromArray([1, 2, 3]); + let l2 = List.fromArray([4, 5, 6]); + let slices = [ + (l1, 0, 1), // [1] + (l1, 1, 2), // [2] + (l2, 2, 3) // [6] + ]; + let result = List.concat(slices); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [1, 2, 6])) + ), + test( + "concat with slices covering full and partial lists", + do { + let l1 = List.fromArray([1, 2, 3]); + let l2 = List.fromArray([4, 5, 6, 7]); + let slices = [ + (l1, 0, 3), // [1,2,3] + (l2, 1, 3) // [5,6] + ]; + let result = List.concat(slices); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [1, 2, 3, 5, 6])) + ), + test( + "concat with repeated slices from the same list", + do { + let l = List.fromArray([9, 8, 7, 6]); + let slices = [ + (l, 0, 2), // [9,8] + (l, 2, 4), // [7,6] + (l, 1, 3) // [8,7] + ]; + let result = List.concat(slices); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [9, 8, 7, 6, 8, 7])) + ), + test( + "concat with a large number of small slices", + do { + let l = List.fromArray(Array.tabulate(20, func(i) = i)); + let slices = Array.tabulate<(List.List, Nat, Nat)>(20, func(i) = (l, i, i + 1)); + let result = List.concat(slices); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, Array.tabulate(20, func(i) = i))) + ) + ] + ) ); \ No newline at end of file From 287a21b6bab0f45b3636fbb4c3820df0c6baa7ef Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 15 May 2025 15:59:34 +0300 Subject: [PATCH 005/123] Fix CI. --- Changelog.md | 4 ++++ src/List.mo | 9 ++++++--- test/List.test.mo | 12 ++++++------ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Changelog.md b/Changelog.md index 9cbbda12d..2f9e6e685 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,10 @@ * Add `entriesFrom` and `reverseEntriesFrom` to `Map`, `valuesFrom` and `reverseValuesFrom` to `Set` and `Text.toText` (#272). * Update code examples in doc comments (#224, #282, #303). +## 0.5.0 + +* Add `concat` of slices function. + ## 0.4.0 * Add `isReplicated : () -> Bool` to `InternetComputer` (#213). diff --git a/src/List.mo b/src/List.mo index c472cd52d..0b0e0fba8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1835,10 +1835,13 @@ module { /// /// Example: /// ```motoko include=import - /// let list1 = List.fromArray([1, 2, 3]); - /// let list2 = List.fromArray([4, 5, 6]); + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// + /// let list1 = List.fromArray([1,2,3]); + /// let list2 = List.fromArray([4,5,6]); /// let result = List.concat([(list1, 0, 2), (list2, 1, 3)]); - /// assert Iter.toArray(List.values(result)) == [1, 2, 5, 6]; + /// assert Iter.toArray(List.values(result)) == [1,2,5,6]; /// ``` /// /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all slices. diff --git a/test/List.test.mo b/test/List.test.mo index b809d84dc..18bc78299 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1380,7 +1380,7 @@ run( let slices = [ (l1, 1, 3), // [11, 12] (l2, 0, 2), // [20, 21] - (l3, 1, 3) // [31, 32] + (l3, 1, 3) // [31, 32] ]; let result = List.concat(slices); List.toArray(result) @@ -1394,7 +1394,7 @@ run( let l2 = List.fromArray([3, 4]); let slices = [ (l1, 0, 0), // [] - (l2, 1, 1) // [] + (l2, 1, 1) // [] ]; let result = List.concat(slices); List.toArray(result) @@ -1409,7 +1409,7 @@ run( let slices = [ (l1, 0, 1), // [1] (l1, 1, 2), // [2] - (l2, 2, 3) // [6] + (l2, 2, 3) // [6] ]; let result = List.concat(slices); List.toArray(result) @@ -1423,7 +1423,7 @@ run( let l2 = List.fromArray([4, 5, 6, 7]); let slices = [ (l1, 0, 3), // [1,2,3] - (l2, 1, 3) // [5,6] + (l2, 1, 3) // [5,6] ]; let result = List.concat(slices); List.toArray(result) @@ -1437,7 +1437,7 @@ run( let slices = [ (l, 0, 2), // [9,8] (l, 2, 4), // [7,6] - (l, 1, 3) // [8,7] + (l, 1, 3) // [8,7] ]; let result = List.concat(slices); List.toArray(result) @@ -1456,4 +1456,4 @@ run( ) ] ) -); \ No newline at end of file +) From f9fe0df7e80debc7aaf1c8e36ac3c502d36edd55 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 26 May 2025 15:42:52 +0300 Subject: [PATCH 006/123] Renamed concat to concat_slices, added concat. --- src/List.mo | 27 ++++++++++++++++++++++++--- test/List.test.mo | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/List.mo b/src/List.mo index 0b0e0fba8..06da923e4 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1840,14 +1840,14 @@ module { /// /// let list1 = List.fromArray([1,2,3]); /// let list2 = List.fromArray([4,5,6]); - /// let result = List.concat([(list1, 0, 2), (list2, 1, 3)]); + /// let result = List.concat_slices([(list1, 0, 2), (list2, 1, 3)]); /// assert Iter.toArray(List.values(result)) == [1,2,5,6]; /// ``` /// /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all slices. /// /// Space: `O(sum_size)` - public func concat(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List { + public func concat_slices(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List { var length = 0; for (slice in slices.vals()) { let (list, start, end) = slice; @@ -1873,5 +1873,26 @@ module { }; result - } + }; + + /// Concatenates the provided lists into a new list. + /// + /// Example: + /// ```motoko include=import + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// + /// let list1 = List.fromArray([1,2,3]); + /// let list2 = List.fromArray([4,5,6]); + /// let result = List.concat([list1, list2]); + /// assert Iter.toArray(List.values(result)) == [1,2,5,6]; + /// ``` + /// + /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all slices. + /// + /// Space: `O(sum_size)` + public func concat(lists : [List]) : List { + concat_slices(Array.tabulate<(List, Nat, Nat)>(lists.size(), func(i) = (lists[i], 0, size(lists[i])))) + }; + } diff --git a/test/List.test.mo b/test/List.test.mo index 18bc78299..df1aafd85 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1328,7 +1328,7 @@ Test.suite( run( suite( - "concat", + "concat slices", [ test( "concat with valid slices", @@ -1337,7 +1337,7 @@ run( let list2 = List.fromArray([4, 5, 6]); let slice1 = (list1, 0, 2); // [1, 2] let slice2 = (list2, 1, 3); // [5, 6] - let result = List.concat([slice1, slice2]); + let result = List.concat_slices([slice1, slice2]); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 5, 6])) @@ -1347,7 +1347,7 @@ run( do { let list1 = List.fromArray([1, 2, 3]); let slice1 = (list1, 1, 1); // [] - let result = List.concat([slice1]); + let result = List.concat_slices([slice1]); List.toArray(result) }, M.equals(T.array(T.natTestable, [] : [Nat])) @@ -1358,7 +1358,7 @@ run( let list1 = List.fromArray([1, 2, 3, 4]); let slice1 = (list1, 0, 2); // [1, 2] let slice2 = (list1, 1, 4); // [2, 3, 4] - let result = List.concat([slice1, slice2]); + let result = List.concat_slices([slice1, slice2]); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 2, 3, 4])) @@ -1369,7 +1369,7 @@ run( run( suite( - "concat (complicated cases)", + "concat slices (complicated cases)", [ test( "concat with many slices from different lists", @@ -1382,7 +1382,7 @@ run( (l2, 0, 2), // [20, 21] (l3, 1, 3) // [31, 32] ]; - let result = List.concat(slices); + let result = List.concat_slices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [11, 12, 20, 21, 31, 32])) @@ -1396,7 +1396,7 @@ run( (l1, 0, 0), // [] (l2, 1, 1) // [] ]; - let result = List.concat(slices); + let result = List.concat_slices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [] : [Nat])) @@ -1411,7 +1411,7 @@ run( (l1, 1, 2), // [2] (l2, 2, 3) // [6] ]; - let result = List.concat(slices); + let result = List.concat_slices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 6])) @@ -1425,7 +1425,7 @@ run( (l1, 0, 3), // [1,2,3] (l2, 1, 3) // [5,6] ]; - let result = List.concat(slices); + let result = List.concat_slices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 3, 5, 6])) @@ -1439,7 +1439,7 @@ run( (l, 2, 4), // [7,6] (l, 1, 3) // [8,7] ]; - let result = List.concat(slices); + let result = List.concat_slices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [9, 8, 7, 6, 8, 7])) @@ -1449,11 +1449,29 @@ run( do { let l = List.fromArray(Array.tabulate(20, func(i) = i)); let slices = Array.tabulate<(List.List, Nat, Nat)>(20, func(i) = (l, i, i + 1)); - let result = List.concat(slices); + let result = List.concat_slices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, Array.tabulate(20, func(i) = i))) ) ] ) +); + +run( + suite( + "concat", + [ + test( + "concat two lists", + do { + let list1 = List.fromArray([1, 2, 3]); + let list2 = List.fromArray([4, 5, 6]); + let result = List.concat([list1, list2]); + List.toArray(result) + }, + M.equals(T.array(T.natTestable, [1, 2, 3, 4, 5, 6])) + ) + ] + ) ) From 89271b032ddf88c72b43beec499a277a7e6d756e Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 26 May 2025 15:54:23 +0300 Subject: [PATCH 007/123] Rename method. --- src/List.mo | 6 +++--- test/List.test.mo | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/List.mo b/src/List.mo index 06da923e4..363c5afdb 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1840,14 +1840,14 @@ module { /// /// let list1 = List.fromArray([1,2,3]); /// let list2 = List.fromArray([4,5,6]); - /// let result = List.concat_slices([(list1, 0, 2), (list2, 1, 3)]); + /// let result = List.concatSlices([(list1, 0, 2), (list2, 1, 3)]); /// assert Iter.toArray(List.values(result)) == [1,2,5,6]; /// ``` /// /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all slices. /// /// Space: `O(sum_size)` - public func concat_slices(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List { + public func concatSlices(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List { var length = 0; for (slice in slices.vals()) { let (list, start, end) = slice; @@ -1892,7 +1892,7 @@ module { /// /// Space: `O(sum_size)` public func concat(lists : [List]) : List { - concat_slices(Array.tabulate<(List, Nat, Nat)>(lists.size(), func(i) = (lists[i], 0, size(lists[i])))) + concatSlices(Array.tabulate<(List, Nat, Nat)>(lists.size(), func(i) = (lists[i], 0, size(lists[i])))) }; } diff --git a/test/List.test.mo b/test/List.test.mo index df1aafd85..316e46662 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1337,7 +1337,7 @@ run( let list2 = List.fromArray([4, 5, 6]); let slice1 = (list1, 0, 2); // [1, 2] let slice2 = (list2, 1, 3); // [5, 6] - let result = List.concat_slices([slice1, slice2]); + let result = List.concatSlices([slice1, slice2]); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 5, 6])) @@ -1347,7 +1347,7 @@ run( do { let list1 = List.fromArray([1, 2, 3]); let slice1 = (list1, 1, 1); // [] - let result = List.concat_slices([slice1]); + let result = List.concatSlices([slice1]); List.toArray(result) }, M.equals(T.array(T.natTestable, [] : [Nat])) @@ -1358,7 +1358,7 @@ run( let list1 = List.fromArray([1, 2, 3, 4]); let slice1 = (list1, 0, 2); // [1, 2] let slice2 = (list1, 1, 4); // [2, 3, 4] - let result = List.concat_slices([slice1, slice2]); + let result = List.concatSlices([slice1, slice2]); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 2, 3, 4])) @@ -1382,7 +1382,7 @@ run( (l2, 0, 2), // [20, 21] (l3, 1, 3) // [31, 32] ]; - let result = List.concat_slices(slices); + let result = List.concatSlices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [11, 12, 20, 21, 31, 32])) @@ -1396,7 +1396,7 @@ run( (l1, 0, 0), // [] (l2, 1, 1) // [] ]; - let result = List.concat_slices(slices); + let result = List.concatSlices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [] : [Nat])) @@ -1411,7 +1411,7 @@ run( (l1, 1, 2), // [2] (l2, 2, 3) // [6] ]; - let result = List.concat_slices(slices); + let result = List.concatSlices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 6])) @@ -1425,7 +1425,7 @@ run( (l1, 0, 3), // [1,2,3] (l2, 1, 3) // [5,6] ]; - let result = List.concat_slices(slices); + let result = List.concatSlices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [1, 2, 3, 5, 6])) @@ -1439,7 +1439,7 @@ run( (l, 2, 4), // [7,6] (l, 1, 3) // [8,7] ]; - let result = List.concat_slices(slices); + let result = List.concatSlices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, [9, 8, 7, 6, 8, 7])) @@ -1449,7 +1449,7 @@ run( do { let l = List.fromArray(Array.tabulate(20, func(i) = i)); let slices = Array.tabulate<(List.List, Nat, Nat)>(20, func(i) = (l, i, i + 1)); - let result = List.concat_slices(slices); + let result = List.concatSlices(slices); List.toArray(result) }, M.equals(T.array(T.natTestable, Array.tabulate(20, func(i) = i))) From 329bd01123175d9bc0a1a422004ac9152f408bf6 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 26 May 2025 16:08:48 +0300 Subject: [PATCH 008/123] Fix. --- src/List.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index 363c5afdb..44e7fc246 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1888,7 +1888,7 @@ module { /// assert Iter.toArray(List.values(result)) == [1,2,5,6]; /// ``` /// - /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all slices. + /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all lists. /// /// Space: `O(sum_size)` public func concat(lists : [List]) : List { From 97e32f72ff0a58e7730951902c82a2b06bc3b3c2 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 26 May 2025 16:27:19 +0300 Subject: [PATCH 009/123] Fix CI/CD. --- mops.toml | 2 +- src/List.mo | 6 +++--- validation/api/api.lock.json | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mops.toml b/mops.toml index 957b4741d..c52b5f43d 100644 --- a/mops.toml +++ b/mops.toml @@ -1,6 +1,6 @@ [package] name = "new-base" -version = "0.4.0" +version = "0.5.0" description = "The new Motoko base library (preview)" repository = "https://github.com/dfinity/new-motoko-base" keywords = [ diff --git a/src/List.mo b/src/List.mo index 0347b5241..9ea2d74ce 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1890,10 +1890,10 @@ module { /// import Nat "mo:base/Nat"; /// import Iter "mo:base/Iter"; /// - /// let list1 = List.fromArray([1,2,3]); - /// let list2 = List.fromArray([4,5,6]); + /// let list1 = List.fromArray([1, 2, 3]); + /// let list2 = List.fromArray([4, 5, 6]); /// let result = List.concat([list1, list2]); - /// assert Iter.toArray(List.values(result)) == [1,2,5,6]; + /// assert Iter.toArray(List.values(result)) == [1, 2, 3, 4, 5, 6]; /// ``` /// /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all lists. diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 6110762ac..aa25fec0c 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -510,6 +510,8 @@ "public func clear(list : List)", "public func clone(list : List) : List", "public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order", + "public func concat(lists : [List]) : List", + "public func concatSlices(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List", "public func contains(list : List, equal : (T, T) -> Bool, element : T) : Bool", "public func empty() : List", "public func entries(list : List) : Iter.Iter<(T, Nat)>", From 232d4bb1b9f26f4b4ecebab704be4b1eff0cd29f Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 26 May 2025 16:30:02 +0300 Subject: [PATCH 010/123] Fix CI/CD. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f384fb49..2a38eb44a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ It's also possible to use both versions in parallel: ```toml base = "0.14.4" -new-base = "0.4.0" +new-base = "0.5.0" ``` Since this is a preview release for community feedback, expect breaking changes. From e3d331442e1e818232727f69cae3938283b9b451 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 26 May 2025 19:14:36 +0300 Subject: [PATCH 011/123] Retrigger CI From cb0295bc18f3af5fd1a8339f615f2e9a282c5236 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 5 Jun 2025 16:39:57 +0300 Subject: [PATCH 012/123] Fix naming accross List. --- src/List.mo | 182 ++++++++++++++++++++++++++-------------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/src/List.mo b/src/List.mo index b34d1dd9d..4443e1cf1 100644 --- a/src/List.mo +++ b/src/List.mo @@ -66,25 +66,25 @@ module { private func repeatInternal(initValue : ?T, size : Nat) : List { let (blockIndex, elementIndex) = locate(size); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let data_blocks = VarArray.repeat<[var ?T]>([var], blocks); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); var i = 1; while (i < blockIndex) { - data_blocks[i] := VarArray.repeat(initValue, data_block_size(i)); + dataBlocks[i] := VarArray.repeat(initValue, dataBlockSize(i)); i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - let block = VarArray.repeat(null, data_block_size(i)); + let block = VarArray.repeat(null, dataBlockSize(i)); var j = 0; while (j < elementIndex) { block[j] := initValue; j += 1 }; - data_blocks[i] := block + dataBlocks[i] := block }; { - var blocks = data_blocks; + var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex } @@ -139,32 +139,32 @@ module { private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { let (blockIndex, elementIndex) = locate(size(list) + count); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let old_blocks = list.blocks.size(); - if (old_blocks < blocks) { - let old_data_blocks = list.blocks; + let oldBlocks = list.blocks.size(); + if (oldBlocks < blocks) { + let oldDataBlocks = list.blocks; list.blocks := VarArray.repeat<[var ?T]>([var], blocks); var i = 0; - while (i < old_blocks) { - list.blocks[i] := old_data_blocks[i]; + while (i < oldBlocks) { + list.blocks[i] := oldDataBlocks[i]; i += 1 } }; var cnt = count; while (cnt > 0) { - let db_size = data_block_size(list.blockIndex); - if (list.elementIndex == 0 and db_size <= cnt) { - list.blocks[list.blockIndex] := VarArray.repeat(initValue, db_size); - cnt -= db_size; + let dbSize = dataBlockSize(list.blockIndex); + if (list.elementIndex == 0 and dbSize <= cnt) { + list.blocks[list.blockIndex] := VarArray.repeat(initValue, dbSize); + cnt -= dbSize; list.blockIndex += 1 } else { if (list.blocks[list.blockIndex].size() == 0) { - list.blocks[list.blockIndex] := VarArray.repeat(null, db_size) + list.blocks[list.blockIndex] := VarArray.repeat(null, dbSize) }; let from = list.elementIndex; - let to = Nat.min(list.elementIndex + cnt, db_size); + let to = Nat.min(list.elementIndex + cnt, dbSize); let block = list.blocks[list.blockIndex]; var i = from; @@ -174,7 +174,7 @@ module { }; list.elementIndex := to; - if (list.elementIndex == db_size) { + if (list.elementIndex == dbSize) { list.elementIndex := 0; list.blockIndex += 1 }; @@ -359,44 +359,44 @@ module { Nat32.toNat((d -% (1 <>> lz)) <>> lz +% i) }; - func data_block_size(blockIndex : Nat) : Nat { + func dataBlockSize(blockIndex : Nat) : Nat { // formula for the size of given blockIndex // don't call it for blockIndex == 0 Nat32.toNat(1 <>> Nat32.bitcountLeadingZero(Nat32.fromNat(blockIndex) / 3)) }; - func new_index_block_length(blockIndex : Nat32) : Nat { + func newIndexBlockLength(blockIndex : Nat32) : Nat { if (blockIndex <= 1) 2 else { let s = 30 - Nat32.bitcountLeadingZero(blockIndex); Nat32.toNat(((blockIndex >> s) +% 1) << s) } }; - func grow_index_block_if_needed(list : List) { + func growIndexBlockIfNeeded(list : List) { if (list.blocks.size() == list.blockIndex) { - let new_blocks = VarArray.repeat<[var ?T]>([var], new_index_block_length(Nat32.fromNat(list.blockIndex))); + let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Nat32.fromNat(list.blockIndex))); var i = 0; while (i < list.blockIndex) { - new_blocks[i] := list.blocks[i]; + newBlocks[i] := list.blocks[i]; i += 1 }; - list.blocks := new_blocks + list.blocks := newBlocks } }; - func shrink_index_block_if_needed(list : List) { + func shrinkIndexBlockIfNeeded(list : List) { let blockIndex = Nat32.fromNat(list.blockIndex); // kind of index of the first block in the super block if ((blockIndex << Nat32.bitcountLeadingZero(blockIndex)) << 2 == 0) { - let new_length = new_index_block_length(blockIndex); - if (new_length < list.blocks.size()) { - let new_blocks = VarArray.repeat<[var ?T]>([var], new_length); + let newLength = newIndexBlockLength(blockIndex); + if (newLength < list.blocks.size()) { + let newBlocks = VarArray.repeat<[var ?T]>([var], newLength); var i = 0; - while (i < new_length) { - new_blocks[i] := list.blocks[i]; + while (i < newLength) { + newBlocks[i] := list.blocks[i]; i += 1 }; - list.blocks := new_blocks + list.blocks := newBlocks } } }; @@ -421,24 +421,24 @@ module { public func add(list : List, element : T) { var elementIndex = list.elementIndex; if (elementIndex == 0) { - grow_index_block_if_needed(list); + growIndexBlockIfNeeded(list); let blockIndex = list.blockIndex; // When removing last we keep one more data block, so can be not empty if (list.blocks[blockIndex].size() == 0) { list.blocks[blockIndex] := VarArray.repeat( null, - data_block_size(blockIndex) + dataBlockSize(blockIndex) ) } }; - let last_data_block = list.blocks[list.blockIndex]; + let lastDataBlock = list.blocks[list.blockIndex]; - last_data_block[elementIndex] := ?element; + lastDataBlock[elementIndex] := ?element; elementIndex += 1; - if (elementIndex == last_data_block.size()) { + if (elementIndex == lastDataBlock.size()) { elementIndex := 0; list.blockIndex += 1 }; @@ -464,7 +464,7 @@ module { public func removeLast(list : List) : ?T { var elementIndex = list.elementIndex; if (elementIndex == 0) { - shrink_index_block_if_needed(list); + shrinkIndexBlockIfNeeded(list); var blockIndex = list.blockIndex; if (blockIndex == 1) { @@ -483,10 +483,10 @@ module { }; elementIndex -= 1; - var last_data_block = list.blocks[list.blockIndex]; + var lastDataBlock = list.blocks[list.blockIndex]; - let element = last_data_block[elementIndex]; - last_data_block[elementIndex] := null; + let element = lastDataBlock[elementIndex]; + lastDataBlock[elementIndex] := null; list.elementIndex := elementIndex; return element @@ -1061,20 +1061,20 @@ module { /// ``` /// /// Runtime: `O(size)` - public func toArray(list : List) : [T] = Array.tabulate(size(list), values_(list).unsafe_next_i); + public func toArray(list : List) : [T] = Array.tabulate(size(list), values_(list).unsafeNextI); private func values_(list : List) : { next : () -> ?T; - unsafe_next : () -> T; - unsafe_next_i : Nat -> T; - next_set : T -> () - } = values_from_(0, list); + unsafeNext : () -> T; + unsafeNextI : Nat -> T; + nextSet : T -> () + } = valuesFrom(0, list); - private func values_from_(start : Nat, list : List) : { + private func valuesFrom(start : Nat, list : List) : { next : () -> ?T; - unsafe_next : () -> T; - unsafe_next_i : Nat -> T; - next_set : T -> () + unsafeNext : () -> T; + unsafeNextI : Nat -> T; + nextSet : T -> () } = object { let blocks = list.blocks.size(); var blockIndex = 0; @@ -1085,15 +1085,15 @@ module { elementIndex := element + 1 }; var db : [var ?T] = list.blocks[blockIndex]; - var db_size = db.size(); + var dbSize = db.size(); public func next() : ?T { - if (elementIndex == db_size) { + if (elementIndex == dbSize) { blockIndex += 1; if (blockIndex >= blocks) return null; db := list.blocks[blockIndex]; - db_size := db.size(); - if (db_size == 0) return null; + dbSize := db.size(); + if (dbSize == 0) return null; elementIndex := 0 }; switch (db[elementIndex]) { @@ -1107,17 +1107,17 @@ module { // version of next() without option type // inlined version of - // public func unsafe_next() : T = { + // public func unsafeNext() : T = { // let ?x = next() else Prim.trap(INTERNAL_ERROR); // x; // }; - public func unsafe_next() : T { - if (elementIndex == db_size) { + public func unsafeNext() : T { + if (elementIndex == dbSize) { blockIndex += 1; if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); db := list.blocks[blockIndex]; - db_size := db.size(); - if (db_size == 0) Prim.trap(INTERNAL_ERROR); + dbSize := db.size(); + if (dbSize == 0) Prim.trap(INTERNAL_ERROR); elementIndex := 0 }; switch (db[elementIndex]) { @@ -1131,14 +1131,14 @@ module { // version of next() without option type and throw-away argument // inlined version of - // public func unsafe_next_(i : Nat) : T = unsafe_next(); - public func unsafe_next_i(i : Nat) : T { - if (elementIndex == db_size) { + // public func unsafeNext_(i : Nat) : T = unsafeNext(); + public func unsafeNextI(i : Nat) : T { + if (elementIndex == dbSize) { blockIndex += 1; if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); db := list.blocks[blockIndex]; - db_size := db.size(); - if (db_size == 0) Prim.trap(INTERNAL_ERROR); + dbSize := db.size(); + if (dbSize == 0) Prim.trap(INTERNAL_ERROR); elementIndex := 0 }; switch (db[elementIndex]) { @@ -1150,13 +1150,13 @@ module { } }; - public func next_set(value : T) { - if (elementIndex == db_size) { + public func nextSet(value : T) { + if (elementIndex == dbSize) { blockIndex += 1; if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); db := list.blocks[blockIndex]; - db_size := db.size(); - if (db_size == 0) Prim.trap(INTERNAL_ERROR); + dbSize := db.size(); + if (dbSize == 0) Prim.trap(INTERNAL_ERROR); elementIndex := 0 }; db[elementIndex] := ?value; @@ -1180,12 +1180,12 @@ module { public func fromArray(array : [T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let data_blocks = VarArray.repeat<[var ?T]>([var], blocks); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); var i = 1; var pos = 0; - func make_block(len : Nat, fill : Nat) : [var ?T] { + func makeBlock(len : Nat, fill : Nat) : [var ?T] { let block = VarArray.repeat(null, len); var j = 0; while (j < fill) { @@ -1197,16 +1197,16 @@ module { }; while (i < blockIndex) { - let len = data_block_size(i); - data_blocks[i] := make_block(len, len); + let len = dataBlockSize(i); + dataBlocks[i] := makeBlock(len, len); i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - data_blocks[i] := make_block(data_block_size(i), elementIndex) + dataBlocks[i] := makeBlock(dataBlockSize(i), elementIndex) }; { - var blocks = data_blocks; + var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex }; @@ -1232,7 +1232,7 @@ module { if (s == 0) return [var]; let arr = VarArray.repeat(Option.unwrap(first(list)), s); var i = 0; - let next = values_(list).unsafe_next; + let next = values_(list).unsafeNext; while (i < s) { arr[i] := next(); i += 1 @@ -1257,12 +1257,12 @@ module { public func fromVarArray(array : [var T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let data_blocks = VarArray.repeat<[var ?T]>([var], blocks); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); var i = 1; var pos = 0; - func make_block(len : Nat, fill : Nat) : [var ?T] { + func makeBlock(len : Nat, fill : Nat) : [var ?T] { let block = VarArray.repeat(null, len); var j = 0; while (j < fill) { @@ -1274,16 +1274,16 @@ module { }; while (i < blockIndex) { - let len = data_block_size(i); - data_blocks[i] := make_block(len, len); + let len = dataBlockSize(i); + dataBlocks[i] := makeBlock(len, len); i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - data_blocks[i] := make_block(data_block_size(i), elementIndex) + dataBlocks[i] := makeBlock(dataBlockSize(i), elementIndex) }; { - var blocks = data_blocks; + var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex }; @@ -1641,8 +1641,8 @@ module { if (size1 != size(list2)) return false; - let next1 = values_(list1).unsafe_next; - let next2 = values_(list2).unsafe_next; + let next1 = values_(list1).unsafeNext; + let next2 = values_(list2).unsafeNext; var i = 0; while (i < size1) { if (not equal(next1(), next2())) return false; @@ -1679,8 +1679,8 @@ module { let size2 = size(list2); let minSize = if (size1 < size2) { size1 } else { size2 }; - let next1 = values_(list1).unsafe_next; - let next2 = values_(list2).unsafe_next; + let next1 = values_(list1).unsafeNext; + let next2 = values_(list2).unsafeNext; var i = 0; while (i < minSize) { switch (compare(next1(), next2())) { @@ -1712,7 +1712,7 @@ module { /// *Runtime and space assumes that `toText` runs in O(1) time and space. public func toText(list : List, f : T -> Text) : Text { let vsize : Int = size(list); - let next = values_(list).unsafe_next; + let next = values_(list).unsafeNext; var i = 0; var text = ""; while (i < vsize - 1) { @@ -1893,11 +1893,11 @@ module { var resultIter = values_(result); for (slice in slices.vals()) { let (list, start, end) = slice; - let values = values_from_(start, list); + let values = valuesFrom(start, list); var i = start; while (i < end) { - let copiedValue = values.unsafe_next(); - resultIter.next_set(copiedValue); + let copiedValue = values.unsafeNext(); + resultIter.nextSet(copiedValue); i += 1 } }; From 7da3bc8275be70e625681849365c1e0137a54252 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 11 Jun 2025 18:46:16 +0300 Subject: [PATCH 013/123] Introduce forEachInternal, rewrite some methods with it. --- src/List.mo | 134 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 35 deletions(-) diff --git a/src/List.mo b/src/List.mo index 4443e1cf1..99840be32 100644 --- a/src/List.mo +++ b/src/List.mo @@ -36,6 +36,13 @@ module { let INTERNAL_ERROR = "List: internal error"; + type IterInternal = { + dataBlock : [var ?T]; + blockIndex : Nat; + elementIndex : Nat; + size : Nat + }; + /// Creates a new empty List for elements of type T. /// /// Example: @@ -630,8 +637,20 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - // inlining would save 10 instructions per entry - findIndex(list, func(x) = equal(element, x)) + var index = 0; + var found = false; + forEachInternal( + list, + func(current) : Bool { + if (equal(element, current)) { + found := true; + return true + }; + index += 1; + false + } + ); + return if (found) ?index else null }; /// Finds the last index of `element` in `list` using equality of elements defined @@ -669,12 +688,18 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func find(list : List, predicate : T -> Bool) : ?T { - for (element in values(list)) { - if (predicate element) { - return ?element + var result : ?T = null; + forEachInternal( + list, + func(element) : Bool { + if (predicate(element)) { + result := ?element; + return true + }; + false } - }; - null + ); + result }; /// Finds the index of the first element in `list` for which `predicate` is true. @@ -696,29 +721,20 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findIndex(list : List, predicate : T -> Bool) : ?Nat { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - var i = 0; - - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return null; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return null; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) if (predicate(x)) return ?i; - case (_) return null - }; - elementIndex += 1; - i += 1 - } + var index = 0; + var found = false; + forEachInternal( + list, + func(element) : Bool { + if (predicate(element)) { + found := true; + return true + }; + index += 1; + false + } + ); + return if (found) ?index else null }; /// Finds the index of the last element in `list` for which `predicate` is true. @@ -787,7 +803,18 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func all(list : List, predicate : T -> Bool) : Bool { - not any(list, func(x) : Bool = not predicate(x)) + var flag = true; + forEachInternal( + list, + func(element) : Bool { + if (not predicate(element)) { + flag := false; + return true + }; + false + } + ); + flag }; /// Returns true iff some element in `list` satisfies `predicate`. @@ -809,10 +836,18 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func any(list : List, predicate : T -> Bool) : Bool { - switch (findIndex(list, predicate)) { - case (null) false; - case (_) true - } + var found = false; + forEachInternal( + list, + func(element) : Bool { + if (predicate(element)) { + found := true; + return true + }; + false + } + ); + found }; /// Returns an Iterator (`Iter`) over the elements of a List. @@ -1328,6 +1363,35 @@ module { if (b == 1) null else list.blocks[b - 1][0] }; + private func forEachInternal( + list : List, + f : T -> Bool + ) { + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + if (f(x)) return; + elementIndex += 1 + }; + case (_) return + } + } + }; + /// Applies `f` to each element in `list`. /// /// Example: From 85aa37a40d8f7d3e675fb865358f6a7fc1d767a0 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 15 Jun 2025 16:46:59 +0300 Subject: [PATCH 014/123] Introduce index in forEachInternal --- src/List.mo | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/List.mo b/src/List.mo index 6fe78f2d5..f75b8d591 100644 --- a/src/List.mo +++ b/src/List.mo @@ -637,20 +637,18 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - var index = 0; - var found = false; + var result : ?Nat = null; forEachInternal( list, - func(current) : Bool { + func(i, current) : Bool { if (equal(element, current)) { - found := true; + result := ?i; return true }; - index += 1; false } ); - return if (found) ?index else null + return result; }; /// Finds the last index of `element` in `list` using equality of elements defined @@ -691,7 +689,7 @@ module { var result : ?T = null; forEachInternal( list, - func(element) : Bool { + func(_, element) : Bool { if (predicate(element)) { result := ?element; return true @@ -721,20 +719,18 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findIndex(list : List, predicate : T -> Bool) : ?Nat { - var index = 0; - var found = false; + var result : ?Nat = null; forEachInternal( list, - func(element) : Bool { + func(i, element) : Bool { if (predicate(element)) { - found := true; + result := ?i; return true }; - index += 1; false } ); - return if (found) ?index else null + return result; }; /// Finds the index of the last element in `list` for which `predicate` is true. @@ -806,7 +802,7 @@ module { var flag = true; forEachInternal( list, - func(element) : Bool { + func(_, element) : Bool { if (not predicate(element)) { flag := false; return true @@ -839,7 +835,7 @@ module { var found = false; forEachInternal( list, - func(element) : Bool { + func(_, element) : Bool { if (predicate(element)) { found := true; return true @@ -1365,13 +1361,14 @@ module { private func forEachInternal( list : List, - f : T -> Bool + f : (counter : Nat, value : T) -> Bool ) { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; var size = 0; var db : [var ?T] = [var]; + var i = 0; loop { if (elementIndex == size) { @@ -1384,8 +1381,9 @@ module { }; switch (db[elementIndex]) { case (?x) { - if (f(x)) return; - elementIndex += 1 + if (f(i, x)) return; + elementIndex += 1; + i += 1 }; case (_) return } From 5e5b3263767859dda9b7e425bb1bfee6bf4eafe0 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 15 Jun 2025 17:00:30 +0300 Subject: [PATCH 015/123] Try findIndex with unsafe iter. --- src/List.mo | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/List.mo b/src/List.mo index f75b8d591..883002f20 100644 --- a/src/List.mo +++ b/src/List.mo @@ -719,18 +719,14 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findIndex(list : List, predicate : T -> Bool) : ?Nat { - var result : ?Nat = null; - forEachInternal( - list, - func(i, element) : Bool { - if (predicate(element)) { - result := ?i; - return true - }; - false - } - ); - return result; + let sz = size(list); + var vals = values_(list); + var i = 0; + while (i < sz) { + if (predicate(vals.unsafeNext())) return ?i; + i += 1; + }; + return null; }; /// Finds the index of the last element in `list` for which `predicate` is true. From 06701c253fab71820b77e07098528b96a6e4717c Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 15 Jun 2025 18:25:37 +0300 Subject: [PATCH 016/123] Try avoid repetInternal. --- src/List.mo | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index 883002f20..cf79761a0 100644 --- a/src/List.mo +++ b/src/List.mo @@ -108,7 +108,11 @@ module { /// Runtime: `O(size)` /// /// Space: `O(size)` - public func repeat(initValue : T, size : Nat) : List = repeatInternal(?initValue, size); + public func repeat(initValue : T, size : Nat) : List { + let list = empty(); + addRepeat(list, initValue, size); + list + }; /// Converts a mutable `List` to a purely functional `PureList`. /// From b497c33d81d5b17a20df1c0556aa9b21ee64ddaf Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 16 Jun 2025 17:29:33 +0300 Subject: [PATCH 017/123] Inline methods. --- src/List.mo | 285 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 202 insertions(+), 83 deletions(-) diff --git a/src/List.mo b/src/List.mo index cf79761a0..2bb0f8e93 100644 --- a/src/List.mo +++ b/src/List.mo @@ -68,7 +68,11 @@ module { /// Runtime: `O(1)` /// /// Space: `O(1)` - public func singleton(element : T) : List = repeat(element, 1); + public func singleton(element : T) : List = { + var blockIndex = 2; + var blocks = [var [var], [var ?element]]; + var elementIndex = 0 + }; private func repeatInternal(initValue : ?T, size : Nat) : List { let (blockIndex, elementIndex) = locate(size); @@ -108,11 +112,7 @@ module { /// Runtime: `O(size)` /// /// Space: `O(size)` - public func repeat(initValue : T, size : Nat) : List { - let list = empty(); - addRepeat(list, initValue, size); - list - }; + public func repeat(initValue : T, size : Nat) : List = repeatInternal(?initValue, size); /// Converts a mutable `List` to a purely functional `PureList`. /// @@ -615,9 +615,7 @@ module { if (size(list) < 2) return; let arr = toVarArray(list); VarArray.sortInPlace(arr, compare); - for (i in arr.keys()) { - put(list, i, arr[i]) - } + forEachEntryChange(list, func(i, _) = arr[i]) }; /// Finds the first index of `element` in `list` using equality of elements defined @@ -641,18 +639,29 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - var result : ?Nat = null; - forEachInternal( - list, - func(i, current) : Bool { - if (equal(element, current)) { - result := ?i; - return true - }; - false - } - ); - return result; + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + var i = 0; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return null; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return null; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) if (equal(x, element)) return ?i; + case (_) return null + }; + elementIndex += 1; + i += 1 + } }; /// Finds the last index of `element` in `list` using equality of elements defined @@ -672,8 +681,32 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - // inlining would save 10 instructions per entry - findLastIndex(list, func(x) = equal(element, x)) + var i = size(list); + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var db : [var ?T] = if (blockIndex < list.blocks.size()) { + list.blocks[blockIndex] + } else { [var] }; + + loop { + if (blockIndex == 1) { + return null + }; + if (elementIndex == 0) { + blockIndex -= 1; + db := list.blocks[blockIndex]; + elementIndex := db.size() - 1 + } else { + elementIndex -= 1 + }; + switch (db[elementIndex]) { + case (?x) { + i -= 1; + if (equal(x, element)) return ?i + }; + case (_) Prim.trap(INTERNAL_ERROR) + } + } }; /// Returns the first value in `list` for which `predicate` returns true. @@ -690,18 +723,7 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func find(list : List, predicate : T -> Bool) : ?T { - var result : ?T = null; - forEachInternal( - list, - func(_, element) : Bool { - if (predicate(element)) { - result := ?element; - return true - }; - false - } - ); - result + Option.map(findIndex(list, predicate), func(i) = get(list, i)) }; /// Finds the index of the first element in `list` for which `predicate` is true. @@ -723,14 +745,29 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findIndex(list : List, predicate : T -> Bool) : ?Nat { - let sz = size(list); - var vals = values_(list); + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; var i = 0; - while (i < sz) { - if (predicate(vals.unsafeNext())) return ?i; - i += 1; - }; - return null; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return null; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return null; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) if (predicate(x)) return ?i; + case (_) return null + }; + elementIndex += 1; + i += 1 + } }; /// Finds the index of the last element in `list` for which `predicate` is true. @@ -799,18 +836,29 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func all(list : List, predicate : T -> Bool) : Bool { - var flag = true; - forEachInternal( - list, - func(_, element) : Bool { - if (not predicate(element)) { - flag := false; - return true - }; - false - } - ); - flag + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + var i = 0; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return true; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return true; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) if (not predicate(x)) return false; + case (_) return true + }; + elementIndex += 1; + i += 1 + } }; /// Returns true iff some element in `list` satisfies `predicate`. @@ -832,18 +880,27 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func any(list : List, predicate : T -> Bool) : Bool { - var found = false; - forEachInternal( - list, - func(_, element) : Bool { - if (predicate(element)) { - found := true; - return true - }; - false - } - ); - found + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return false; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return false; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) if (predicate(x)) return true; + case (_) return false + }; + elementIndex += 1 + } }; /// Returns an Iterator (`Iter`) over the elements of a List. @@ -1111,7 +1168,7 @@ module { var blockIndex = 0; var elementIndex = 0; if (start != 0) { - let (block, element) = if (start == 0) (0, 0) else locate(start - 1); + let (block, element) = locate(start - 1); blockIndex := block; elementIndex := element + 1 }; @@ -1333,7 +1390,7 @@ module { /// /// Space: `O(1)` public func first(list : List) : ?T { - if (isEmpty(list)) null else list.blocks[1][0] + if (list.blockIndex == 1 and list.elementIndex == 0) null else list.blocks[1][0] }; /// Returns the last element of `list`. Traps if `list` is empty. @@ -1359,9 +1416,9 @@ module { if (b == 1) null else list.blocks[b - 1][0] }; - private func forEachInternal( + public func forEachEntryChange( list : List, - f : (counter : Nat, value : T) -> Bool + f : (i : Nat, oldValue : T) -> (newValue : T) ) { let blocks = list.blocks.size(); var blockIndex = 0; @@ -1381,9 +1438,38 @@ module { }; switch (db[elementIndex]) { case (?x) { - if (f(i, x)) return; - elementIndex += 1; - i += 1 + db[elementIndex] := ?f(i, x); + elementIndex += 1 + }; + case (_) return + }; + i += 1 + } + }; + + public func forEachChange( + list : List, + f : (oldValue : T) -> (newValue : T) + ) { + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + db[elementIndex] := ?f(x); + elementIndex += 1 }; case (_) return } @@ -1810,12 +1896,29 @@ module { public func foldLeft(list : List, base : A, combine : (A, T) -> A) : A { var accumulation = base; - forEach( - list, - func(x) = accumulation := combine(accumulation, x) - ); + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; - accumulation + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return accumulation; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return accumulation; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + accumulation := combine(accumulation, x); + elementIndex += 1 + }; + case (_) return accumulation + } + } }; /// Collapses the elements in `list` into a single value by starting with `base` @@ -1839,12 +1942,28 @@ module { public func foldRight(list : List, base : A, combine : (T, A) -> A) : A { var accumulation = base; - reverseForEach( - list, - func(x) = accumulation := combine(x, accumulation) - ); + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var db : [var ?T] = if (blockIndex < list.blocks.size()) { + list.blocks[blockIndex] + } else { [var] }; - accumulation + loop { + if (blockIndex == 1) { + return accumulation + }; + if (elementIndex == 0) { + blockIndex -= 1; + db := list.blocks[blockIndex]; + elementIndex := db.size() - 1 + } else { + elementIndex -= 1 + }; + switch (db[elementIndex]) { + case (?x) accumulation := combine(x, accumulation); + case (_) Prim.trap(INTERNAL_ERROR) + } + } }; /// Reverses the order of elements in `list` by overwriting in place. From 5483683d3a3d68a6f28636821ad10427d6c8edd1 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 16 Jun 2025 23:00:19 +0300 Subject: [PATCH 018/123] Optimize functions. --- src/List.mo | 260 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 177 insertions(+), 83 deletions(-) diff --git a/src/List.mo b/src/List.mo index 2bb0f8e93..2576b93b5 100644 --- a/src/List.mo +++ b/src/List.mo @@ -36,13 +36,6 @@ module { let INTERNAL_ERROR = "List: internal error"; - type IterInternal = { - dataBlock : [var ?T]; - blockIndex : Nat; - elementIndex : Nat; - size : Nat - }; - /// Creates a new empty List for elements of type T. /// /// Example: @@ -240,10 +233,7 @@ module { public func clone(list : List) : List = { var blocks = VarArray.tabulate<[var ?T]>( list.blocks.size(), - func(i) = VarArray.tabulate( - list.blocks[i].size(), - func(j) = list.blocks[i][j] - ) + func(i) = VarArray.clone(list.blocks[i]) ); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex @@ -296,13 +286,30 @@ module { /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func filter(list : List, predicate : T -> Bool) : List { let filtered = empty(); - forEach( - list, - func(x) { - if (predicate(x)) add(filtered, x) + + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return filtered; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return filtered; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + if (predicate(x)) add(filtered, x); + elementIndex += 1 + }; + case (_) return filtered } - ); - filtered + } }; /// Returns a new list containing all elements from `list` for which the function returns ?element. @@ -322,16 +329,33 @@ module { /// *Runtime and space assumes that `f` runs in `O(1)` time and space. public func filterMap(list : List, f : T -> ?R) : List { let filtered = empty(); - forEach( - list, - func(x) { - switch (f(x)) { - case (?y) add(filtered, y); - case null {} - } + + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return filtered; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return filtered; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + switch (f(x)) { + case (?y) add(filtered, y); + case null {} + }; + elementIndex += 1 + }; + case (_) return filtered } - ); - filtered + } }; /// Returns the current number of elements in the list. @@ -1693,6 +1717,36 @@ module { Option.isSome(indexOf(list, equal, element)) }; + private func minMax(list : List, compare : (T, T) -> Order.Order, compareResult : Order.Order) : ?T { + if (isEmpty(list)) return null; + + var extremum = get(list, 0); + + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + + loop { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return ?extremum; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return ?extremum; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + if (compare(x, extremum) == compareResult) extremum := x; + elementIndex += 1 + }; + case (_) return ?extremum + } + } + }; + /// Returns the greatest element in the list according to the ordering defined by `compare`. /// Returns `null` if the list is empty. /// @@ -1713,20 +1767,7 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func max(list : List, compare : (T, T) -> Order.Order) : ?T { - if (isEmpty(list)) return null; - - var maxSoFar = get(list, 0); - forEach( - list, - func(x) = switch (compare(x, maxSoFar)) { - case (#greater) maxSoFar := x; - case _ {} - } - ); - - return ?maxSoFar - }; + public func max(list : List, compare : (T, T) -> Order.Order) : ?T = minMax(list, compare, #greater); /// Returns the least element in the list according to the ordering defined by `compare`. /// Returns `null` if the list is empty. @@ -1748,20 +1789,7 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func min(list : List, compare : (T, T) -> Order.Order) : ?T { - if (isEmpty(list)) return null; - - var minSoFar = get(list, 0); - forEach( - list, - func(x) = switch (compare(x, minSoFar)) { - case (#less) minSoFar := x; - case _ {} - } - ); - - return ?minSoFar - }; + public func min(list : List, compare : (T, T) -> Order.Order) : ?T = minMax(list, compare, #less); /// Tests if two lists are equal by comparing their elements using the provided `equal` function. /// Returns true if and only if both lists have the same size and all corresponding elements @@ -1789,15 +1817,31 @@ module { if (size1 != size(list2)) return false; - let next1 = values_(list1).unsafeNext; - let next2 = values_(list2).unsafeNext; - var i = 0; - while (i < size1) { - if (not equal(next1(), next2())) return false; - i += 1 - }; + let blocks = Nat.min(list1.blocks.size(), list2.blocks.size()); + var blockIndex = 0; + var elementIndex = 0; + var sz = 0; + var db1 : [var ?T] = [var]; + var db2 : [var ?T] = [var]; - return true + loop { + if (elementIndex == sz) { + blockIndex += 1; + if (blockIndex >= blocks) return true; + db1 := list1.blocks[blockIndex]; + db2 := list2.blocks[blockIndex]; + sz := db1.size(); + if (sz == 0) return true; + elementIndex := 0 + }; + switch (db1[elementIndex], db2[elementIndex]) { + case (?x, ?y) { + if (not equal(x, y)) return false; + elementIndex += 1 + }; + case (_) return true + } + } }; /// Compares two lists lexicographically using the provided `compare` function. @@ -1825,20 +1869,36 @@ module { public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order { let size1 = size(list1); let size2 = size(list2); - let minSize = if (size1 < size2) { size1 } else { size2 }; - let next1 = values_(list1).unsafeNext; - let next2 = values_(list2).unsafeNext; - var i = 0; - while (i < minSize) { - switch (compare(next1(), next2())) { - case (#less) return #less; - case (#greater) return #greater; - case _ {} + let blocks = Nat.min(list1.blocks.size(), list2.blocks.size()); + var blockIndex = 0; + var elementIndex = 0; + var sz = 0; + var db1 : [var ?T] = [var]; + var db2 : [var ?T] = [var]; + + loop { + if (elementIndex == sz) { + blockIndex += 1; + if (blockIndex >= blocks) return Nat.compare(size1, size2); + db1 := list1.blocks[blockIndex]; + db2 := list2.blocks[blockIndex]; + sz := db1.size(); + if (sz == 0) return Nat.compare(size1, size2); + elementIndex := 0 }; - i += 1 - }; - Nat.compare(size1, size2) + switch (db1[elementIndex], db2[elementIndex]) { + case (?x, ?y) { + switch (compare(x, y)) { + case (#less) return #less; + case (#greater) return #greater; + case _ {} + }; + elementIndex += 1 + }; + case (_) return Nat.compare(size1, size2) + } + } }; /// Creates a textual representation of `list`, using `toText` to recursively @@ -1984,17 +2044,51 @@ module { /// Space: `O(1)` public func reverseInPlace(list : List) { let vsize = size(list); - if (vsize == 0) return; + if (vsize <= 1) return; + let count = vsize / 2; var i = 0; - var j = vsize - 1 : Nat; - var temp = get(list, 0); - while (i < vsize / 2) { - temp := get(list, j); - put(list, j, get(list, i)); - put(list, i, temp); - i += 1; - j -= 1 + + let blocks = list.blocks.size(); + var blockIndexFront = 0; + var elementIndexFront = 0; + var sz = 0; + var dbFront : [var ?T] = [var]; + + var blockIndexBack = list.blockIndex; + var elementIndexBack = list.elementIndex; + var dbBack : [var ?T] = if (blockIndexBack < list.blocks.size()) { + list.blocks[blockIndexBack] + } else { [var] }; + + while (i < count) { + if (elementIndexFront == sz) { + blockIndexFront += 1; + if (blockIndexFront >= blocks) return; + dbFront := list.blocks[blockIndexFront]; + sz := dbFront.size(); + if (sz == 0) return; + elementIndexFront := 0 + }; + + if (blockIndexBack == 1) { + return + }; + if (elementIndexBack == 0) { + blockIndexBack -= 1; + dbBack := list.blocks[blockIndexBack]; + elementIndexBack := dbBack.size() - 1 + } else { + elementIndexBack -= 1 + }; + + let temp = dbFront[elementIndexFront]; + dbFront[elementIndexFront] := dbBack[elementIndexBack]; + dbBack[elementIndexBack] := temp; + + elementIndexFront += 1; + + i += 1 } }; From 32d48fb3ec64ba15e9e1246ab1f9d7b80043f45a Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 17 Jun 2025 23:04:17 +0300 Subject: [PATCH 019/123] Refactor fromArray. --- src/List.mo | 45 ++++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/List.mo b/src/List.mo index 2576b93b5..3a13a148d 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1290,39 +1290,30 @@ module { /// /// Runtime: `O(size)` public func fromArray(array : [T]) : List { - let (blockIndex, elementIndex) = locate(array.size()); + let sz = array.size(); + let list = repeatInternal(null, sz); - let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); - var i = 1; - var pos = 0; + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var size = 0; + var db : [var ?T] = [var]; + var i = 0; - func makeBlock(len : Nat, fill : Nat) : [var ?T] { - let block = VarArray.repeat(null, len); - var j = 0; - while (j < fill) { - block[j] := ?array[pos]; - j += 1; - pos += 1 + while (i < sz) { + if (elementIndex == size) { + blockIndex += 1; + if (blockIndex >= blocks) return list; + db := list.blocks[blockIndex]; + size := db.size(); + if (size == 0) return list; + elementIndex := 0 }; - block - }; - - while (i < blockIndex) { - let len = dataBlockSize(i); - dataBlocks[i] := makeBlock(len, len); + db[elementIndex] := ?array[i]; i += 1 }; - if (elementIndex != 0 and blockIndex < blocks) { - dataBlocks[i] := makeBlock(dataBlockSize(i), elementIndex) - }; - - { - var blocks = dataBlocks; - var blockIndex = blockIndex; - var elementIndex = elementIndex - }; + list }; /// Creates a new mutable array containing all elements from the list. From 7f95888944ec939b2ce8d4cc2427ae540f512e05 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 17 Jun 2025 23:14:22 +0300 Subject: [PATCH 020/123] Fix. --- src/List.mo | 1 + 1 file changed, 1 insertion(+) diff --git a/src/List.mo b/src/List.mo index 3a13a148d..9e7d6fad3 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1310,6 +1310,7 @@ module { elementIndex := 0 }; db[elementIndex] := ?array[i]; + elementIndex += 1; i += 1 }; From 6b0e8b840bcb665935c7dcfe89710cf18219d48d Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 17 Jun 2025 23:24:55 +0300 Subject: [PATCH 021/123] Revert to original fromArray. Oprimize original fromArray. --- src/List.mo | 64 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/List.mo b/src/List.mo index 9e7d6fad3..b2006704a 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1290,31 +1290,41 @@ module { /// /// Runtime: `O(size)` public func fromArray(array : [T]) : List { - let sz = array.size(); - let list = repeatInternal(null, sz); + let (blockIndex, elementIndex) = locate(array.size()); - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - var i = 0; + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); - while (i < sz) { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return list; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return list; - elementIndex := 0 + func makeBlock(array : [T], p : Nat, len : Nat, fill : Nat) : [var ?T] { + let block = VarArray.repeat(null, len); + var j = 0; + var pos = p; + while (j < fill) { + block[j] := ?array[pos]; + j += 1; + pos += 1 }; - db[elementIndex] := ?array[i]; - elementIndex += 1; + block + }; + + var i = 1; + var pos = 0; + + while (i < blockIndex) { + let len = dataBlockSize(i); + dataBlocks[i] := makeBlock(array, pos, len, len); + pos += len; i += 1 }; + if (elementIndex != 0 and blockIndex < blocks) { + dataBlocks[i] := makeBlock(array, pos, dataBlockSize(i), elementIndex) + }; - list + { + var blocks = dataBlocks; + var blockIndex = blockIndex; + var elementIndex = elementIndex + } }; /// Creates a new mutable array containing all elements from the list. @@ -1363,12 +1373,11 @@ module { let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); - var i = 1; - var pos = 0; - func makeBlock(len : Nat, fill : Nat) : [var ?T] { + func makeBlock(array : [var T], p : Nat, len : Nat, fill : Nat) : [var ?T] { let block = VarArray.repeat(null, len); var j = 0; + var pos = p; while (j < fill) { block[j] := ?array[pos]; j += 1; @@ -1377,21 +1386,24 @@ module { block }; + var i = 1; + var pos = 0; + while (i < blockIndex) { let len = dataBlockSize(i); - dataBlocks[i] := makeBlock(len, len); + dataBlocks[i] := makeBlock(array, pos, len, len); + pos += len; i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - dataBlocks[i] := makeBlock(dataBlockSize(i), elementIndex) + dataBlocks[i] := makeBlock(array, pos, dataBlockSize(i), elementIndex) }; { var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex - }; - + } }; /// Returns the first element of `list`, or `null` if the list is empty. From a134d672380fc9eeeebf824617ba660610fc3883 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 17 Jun 2025 23:41:24 +0300 Subject: [PATCH 022/123] Optimize reverse. --- src/List.mo | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/List.mo b/src/List.mo index b2006704a..3eb7e9d46 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2113,14 +2113,45 @@ module { /// /// Space: `O(1)` public func reverse(list : List) : List { - let rlist = empty(); + let rlist = repeatInternal(null, size(list)); - reverseForEach( - list, - func(x) = add(rlist, x) - ); + let blocks = list.blocks.size(); + var blockIndexFront = 0; + var elementIndexFront = 0; + var sz = 0; + var dbFront : [var ?T] = [var]; + + var blockIndexBack = rlist.blockIndex; + var elementIndexBack = rlist.elementIndex; + var dbBack : [var ?T] = if (blockIndexBack < rlist.blocks.size()) { + rlist.blocks[blockIndexBack] + } else { [var] }; - rlist + loop { + if (elementIndexFront == sz) { + blockIndexFront += 1; + if (blockIndexFront >= blocks) return rlist; + dbFront := list.blocks[blockIndexFront]; + sz := dbFront.size(); + if (sz == 0) return rlist; + elementIndexFront := 0 + }; + + if (blockIndexBack == 1) { + return rlist + }; + if (elementIndexBack == 0) { + blockIndexBack -= 1; + dbBack := rlist.blocks[blockIndexBack]; + elementIndexBack := dbBack.size() - 1 + } else { + elementIndexBack -= 1 + }; + + dbBack[elementIndexBack] := dbFront[elementIndexFront]; + + elementIndexFront += 1 + } }; /// Returns true if and only if the list is empty. From 03d682bd3c5d7ef6465384aed60420269fee9205 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 17 Jun 2025 23:46:33 +0300 Subject: [PATCH 023/123] Optimize indexOf. --- src/List.mo | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/List.mo b/src/List.mo index 3eb7e9d46..e898097e0 100644 --- a/src/List.mo +++ b/src/List.mo @@ -666,25 +666,27 @@ module { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; - var size = 0; + var sz = 0; var db : [var ?T] = [var]; - var i = 0; loop { - if (elementIndex == size) { + if (elementIndex == sz) { blockIndex += 1; if (blockIndex >= blocks) return null; db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return null; + sz := db.size(); + if (sz == 0) return null; elementIndex := 0 }; switch (db[elementIndex]) { - case (?x) if (equal(x, element)) return ?i; + case (?x) if (equal(x, element)) return ?size({ + var blocks = [var]; + var blockIndex = blockIndex; + var elementIndex = elementIndex + }); case (_) return null }; elementIndex += 1; - i += 1 } }; From ea3b4a43749297be36d9d541ff8b3c6232b24478 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 17 Jun 2025 23:50:00 +0300 Subject: [PATCH 024/123] Remove similar methods. --- src/List.mo | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/List.mo b/src/List.mo index e898097e0..9db883630 100644 --- a/src/List.mo +++ b/src/List.mo @@ -686,7 +686,7 @@ module { }); case (_) return null }; - elementIndex += 1; + elementIndex += 1 } }; @@ -707,7 +707,6 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - var i = size(list); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex; var db : [var ?T] = if (blockIndex < list.blocks.size()) { @@ -727,8 +726,11 @@ module { }; switch (db[elementIndex]) { case (?x) { - i -= 1; - if (equal(x, element)) return ?i + if (equal(x, element)) return ?size({ + var blocks = [var]; + var blockIndex = blockIndex; + var elementIndex = elementIndex + }) }; case (_) Prim.trap(INTERNAL_ERROR) } @@ -774,25 +776,27 @@ module { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; - var size = 0; + var sz = 0; var db : [var ?T] = [var]; - var i = 0; loop { - if (elementIndex == size) { + if (elementIndex == sz) { blockIndex += 1; if (blockIndex >= blocks) return null; db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return null; + sz := db.size(); + if (sz == 0) return null; elementIndex := 0 }; switch (db[elementIndex]) { - case (?x) if (predicate(x)) return ?i; + case (?x) if (predicate(x)) return ?size({ + var blocks = [var]; + var blockIndex = blockIndex; + var elementIndex = elementIndex + }); case (_) return null }; - elementIndex += 1; - i += 1 + elementIndex += 1 } }; @@ -815,7 +819,6 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findLastIndex(list : List, predicate : T -> Bool) : ?Nat { - var i = size(list); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex; var db : [var ?T] = if (blockIndex < list.blocks.size()) { @@ -835,8 +838,11 @@ module { }; switch (db[elementIndex]) { case (?x) { - i -= 1; - if (predicate(x)) return ?i + if (predicate(x)) return ?size({ + var blocks = [var]; + var blockIndex = blockIndex; + var elementIndex = elementIndex + }) }; case (_) Prim.trap(INTERNAL_ERROR) } @@ -867,7 +873,6 @@ module { var elementIndex = 0; var size = 0; var db : [var ?T] = [var]; - var i = 0; loop { if (elementIndex == size) { @@ -883,7 +888,6 @@ module { case (_) return true }; elementIndex += 1; - i += 1 } }; From 88364866641dbf6e9c7720fb17494c26c6192b65 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 00:16:07 +0300 Subject: [PATCH 025/123] Optimize repeatInternal and addRepeatInternal for null initValue. --- src/List.mo | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/List.mo b/src/List.mo index 9db883630..8ba889ae8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -79,10 +79,12 @@ module { }; if (elementIndex != 0 and blockIndex < blocks) { let block = VarArray.repeat(null, dataBlockSize(i)); - var j = 0; - while (j < elementIndex) { - block[j] := initValue; - j += 1 + if (not Option.isNull(initValue)) { + var j = 0; + while (j < elementIndex) { + block[j] := initValue; + j += 1 + } }; dataBlocks[i] := block }; @@ -171,10 +173,12 @@ module { let to = Nat.min(list.elementIndex + cnt, dbSize); let block = list.blocks[list.blockIndex]; - var i = from; - while (i < to) { - block[i] := initValue; - i += 1 + if (not Option.isNull(initValue)) { + var i = from; + while (i < to) { + block[i] := initValue; + i += 1 + } }; list.elementIndex := to; @@ -887,7 +891,7 @@ module { case (?x) if (not predicate(x)) return false; case (_) return true }; - elementIndex += 1; + elementIndex += 1 } }; From 04ffa2e427ff22ce8eb4b243298547bd3ed78d99 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 16:14:33 +0300 Subject: [PATCH 026/123] Optimize reverse iterators. --- src/List.mo | 29 ++++++++--------------------- test/List.test.mo | 8 ++++++++ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/List.mo b/src/List.mo index 8ba889ae8..0587481b6 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,6 +14,7 @@ import PureList "pure/List"; import Prim "mo:⛔"; +import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -718,11 +719,9 @@ module { } else { [var] }; loop { - if (blockIndex == 1) { - return null - }; if (elementIndex == 0) { blockIndex -= 1; + if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 } else { @@ -830,11 +829,9 @@ module { } else { [var] }; loop { - if (blockIndex == 1) { - return null - }; if (elementIndex == 0) { blockIndex -= 1; + if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 } else { @@ -1041,11 +1038,9 @@ module { } else { [var] }; public func next() : ?T { - if (blockIndex == 1) { - return null - }; if (elementIndex == 0) { blockIndex -= 1; + if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 } else { @@ -1086,11 +1081,9 @@ module { } else { [var] }; public func next() : ?(Nat, T) { - if (blockIndex == 1) { - return null - }; if (elementIndex == 0) { blockIndex -= 1; + if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 } else { @@ -1645,11 +1638,9 @@ module { var i = size(list); loop { - if (blockIndex == 1) { - return - }; if (elementIndex == 0) { blockIndex -= 1; + if (blockIndex == 0) return; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 } else { @@ -1690,11 +1681,9 @@ module { } else { [var] }; loop { - if (blockIndex == 1) { - return - }; if (elementIndex == 0) { blockIndex -= 1; + if (blockIndex == 0) return; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 } else { @@ -2023,11 +2012,9 @@ module { } else { [var] }; loop { - if (blockIndex == 1) { - return accumulation - }; if (elementIndex == 0) { blockIndex -= 1; + if (blockIndex == 0) return accumulation; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 } else { diff --git a/test/List.test.mo b/test/List.test.mo index 69883fbf8..d2f0ffbc3 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -15,6 +15,14 @@ import Int "../src/Int"; import Debug "../src/Debug"; import { Tuple2 } "../src/Tuples"; + +// let l = List.empty(); +// List.add(l, 0); + +// let ix = List.lastIndexOf(l, func (x, y) = x == y, 1); + +// Runtime.trap(""); + let { run; test; suite } = Suite; func unwrap(x : ?T) : T = switch (x) { From ff8d8b5aa44db899d8ea93377dac11350cd81580 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 16:22:09 +0300 Subject: [PATCH 027/123] Optimize reverseForEach. --- src/List.mo | 7 +++---- test/List.test.mo | 8 -------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/List.mo b/src/List.mo index 0587481b6..2b55022ba 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,7 +14,6 @@ import PureList "pure/List"; import Prim "mo:⛔"; -import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -1681,13 +1680,13 @@ module { } else { [var] }; loop { - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; if (blockIndex == 0) return; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; switch (db[elementIndex]) { case (?x) f(x); diff --git a/test/List.test.mo b/test/List.test.mo index d2f0ffbc3..69883fbf8 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -15,14 +15,6 @@ import Int "../src/Int"; import Debug "../src/Debug"; import { Tuple2 } "../src/Tuples"; - -// let l = List.empty(); -// List.add(l, 0); - -// let ix = List.lastIndexOf(l, func (x, y) = x == y, 1); - -// Runtime.trap(""); - let { run; test; suite } = Suite; func unwrap(x : ?T) : T = switch (x) { From e53679e91834b80d382bc4b8fc430dfddadece24 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 16:26:34 +0300 Subject: [PATCH 028/123] Optimize reverse iterators. --- src/List.mo | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/List.mo b/src/List.mo index 2b55022ba..c8b38b1e9 100644 --- a/src/List.mo +++ b/src/List.mo @@ -718,13 +718,13 @@ module { } else { [var] }; loop { - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; switch (db[elementIndex]) { case (?x) { @@ -828,15 +828,14 @@ module { } else { [var] }; loop { - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 - }; - switch (db[elementIndex]) { + }; switch (db[elementIndex]) { case (?x) { if (predicate(x)) return ?size({ var blocks = [var]; @@ -1037,13 +1036,13 @@ module { } else { [var] }; public func next() : ?T { - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; db[elementIndex] @@ -1080,13 +1079,13 @@ module { } else { [var] }; public func next() : ?(Nat, T) { - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; switch (db[elementIndex]) { case (?x) { @@ -1637,13 +1636,13 @@ module { var i = size(list); loop { - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; if (blockIndex == 0) return; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; i -= 1; switch (db[elementIndex]) { @@ -2011,13 +2010,13 @@ module { } else { [var] }; loop { - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; if (blockIndex == 0) return accumulation; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; switch (db[elementIndex]) { case (?x) accumulation := combine(x, accumulation); From 4d2c8773b711ad4eac7b390b58c60b36de19747e Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 16:45:35 +0300 Subject: [PATCH 029/123] Optimize iterators in reverse, reversInPlace. --- src/List.mo | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/List.mo b/src/List.mo index c8b38b1e9..2c0ffaede 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2070,11 +2070,9 @@ module { elementIndexFront := 0 }; - if (blockIndexBack == 1) { - return - }; if (elementIndexBack == 0) { blockIndexBack -= 1; + if (blockIndexBack == 0) return; dbBack := list.blocks[blockIndexBack]; elementIndexBack := dbBack.size() - 1 } else { @@ -2132,11 +2130,9 @@ module { elementIndexFront := 0 }; - if (blockIndexBack == 1) { - return rlist - }; if (elementIndexBack == 0) { blockIndexBack -= 1; + if (blockIndexBack == 0) return rlist; dbBack := rlist.blocks[blockIndexBack]; elementIndexBack := dbBack.size() - 1 } else { From a82e8d387e14ee2896a47297a9aaaac8faac8145 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 18:43:52 +0300 Subject: [PATCH 030/123] Optimize toText, toArray, toVarArray. Fix bug in last. --- src/List.mo | 139 ++++++++++++++++++++++++++++++---------------- test/List.test.mo | 5 ++ 2 files changed, 97 insertions(+), 47 deletions(-) diff --git a/src/List.mo b/src/List.mo index 2c0ffaede..a257eed25 100644 --- a/src/List.mo +++ b/src/List.mo @@ -835,7 +835,8 @@ module { if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - }; switch (db[elementIndex]) { + }; + switch (db[elementIndex]) { case (?x) { if (predicate(x)) return ?size({ var blocks = [var]; @@ -1174,19 +1175,43 @@ module { /// ``` /// /// Runtime: `O(size)` - public func toArray(list : List) : [T] = Array.tabulate(size(list), values_(list).unsafeNextI); + public func toArray(list : List) : [T] { + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var sz = 0; + var db : [var ?T] = [var]; + + func generator(_ : Nat) : T { + if (elementIndex == sz) { + blockIndex += 1; + if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); + db := list.blocks[blockIndex]; + sz := db.size(); + if (sz == 0) Prim.trap(INTERNAL_ERROR); + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + elementIndex += 1; + return x + }; + case (_) Prim.trap(INTERNAL_ERROR) + } + }; + + Array.tabulate(size(list), generator) + }; private func values_(list : List) : { next : () -> ?T; unsafeNext : () -> T; - unsafeNextI : Nat -> T; nextSet : T -> () } = valuesFrom(0, list); private func valuesFrom(start : Nat, list : List) : { next : () -> ?T; unsafeNext : () -> T; - unsafeNextI : Nat -> T; nextSet : T -> () } = object { let blocks = list.blocks.size(); @@ -1242,27 +1267,6 @@ module { } }; - // version of next() without option type and throw-away argument - // inlined version of - // public func unsafeNext_(i : Nat) : T = unsafeNext(); - public func unsafeNextI(i : Nat) : T { - if (elementIndex == dbSize) { - blockIndex += 1; - if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); - db := list.blocks[blockIndex]; - dbSize := db.size(); - if (dbSize == 0) Prim.trap(INTERNAL_ERROR); - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - elementIndex += 1; - return x - }; - case (_) Prim.trap(INTERNAL_ERROR) - } - }; - public func nextSet(value : T) { if (elementIndex == dbSize) { blockIndex += 1; @@ -1345,14 +1349,33 @@ module { public func toVarArray(list : List) : [var T] { let s = size(list); if (s == 0) return [var]; - let arr = VarArray.repeat(Option.unwrap(first(list)), s); + + let array = VarArray.repeat(Option.unwrap(first(list)), s); + + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var sz = 0; + var db : [var ?T] = [var]; var i = 0; - let next = values_(list).unsafeNext; - while (i < s) { - arr[i] := next(); + + loop { + if (elementIndex == sz) { + blockIndex += 1; + if (blockIndex >= blocks) return array; + db := list.blocks[blockIndex]; + sz := db.size(); + if (sz == 0) return array; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) array[i] := x; + case (_) return array + }; + elementIndex += 1; i += 1 }; - arr + array }; /// Creates a new List containing all elements from the mutable array. @@ -1437,12 +1460,13 @@ module { let e = list.elementIndex; if (e > 0) { switch (list.blocks[list.blockIndex][e - 1]) { - case null { Prim.trap(INTERNAL_ERROR) }; - case e { return e } + case null Prim.trap(INTERNAL_ERROR); + case e return e; } }; - let b = list.blockIndex; - if (b == 1) null else list.blocks[b - 1][0] + let b = list.blockIndex - 1 : Nat; + let blocks = list.blocks; + if (b == 0) null else blocks[b][blocks[b].size() - 1] }; public func forEachEntryChange( @@ -1528,16 +1552,16 @@ module { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; - var size = 0; + var sz = 0; var db : [var ?T] = [var]; loop { - if (elementIndex == size) { + if (elementIndex == sz) { blockIndex += 1; if (blockIndex >= blocks) return; db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return; + sz := db.size(); + if (sz == 0) return; elementIndex := 0 }; switch (db[elementIndex]) { @@ -1920,18 +1944,39 @@ module { /// /// *Runtime and space assumes that `toText` runs in O(1) time and space. public func toText(list : List, f : T -> Text) : Text { - let vsize : Int = size(list); - let next = values_(list).unsafeNext; - var i = 0; + let vsize = size(list); + if (vsize == 0) return "List[]"; + + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var sz = 0; + var db : [var ?T] = [var]; + + var i = 1; var text = ""; - while (i < vsize - 1) { - text := text # f(next()) # ", "; // Text implemented as rope + + while (i < vsize) { + if (elementIndex == sz) { + blockIndex += 1; + if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); + db := list.blocks[blockIndex]; + sz := db.size(); + if (sz == 0) Prim.trap(INTERNAL_ERROR); + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + text := text # f(x) # ", "; // Text implemented as rope + elementIndex += 1 + }; + case (_) Prim.trap(INTERNAL_ERROR) + }; i += 1 }; - if (vsize > 0) { - // avoid the trailing comma - text := text # f(get(list, i)) - }; + + // avoid the trailing comma + text := text # f(Option.unwrap(last(list))); "List[" # text # "]" }; diff --git a/test/List.test.mo b/test/List.test.mo index 69883fbf8..45c693f24 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -285,6 +285,11 @@ run( List.last(List.repeat(1, 1)), M.equals(T.optional(T.natTestable, ?1)) ), + test( + "last of 6", + List.last(List.fromArray([0, 1, 2, 3, 4, 5])), + M.equals(T.optional(T.natTestable, ?5)) + ), test( "last empty", List.last(List.empty()), From 342468f89841ce1b5dfde6e31f543f0e283dc61f Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 19:44:10 +0300 Subject: [PATCH 031/123] Optimize map. --- src/List.mo | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/List.mo b/src/List.mo index a257eed25..5f5200711 100644 --- a/src/List.mo +++ b/src/List.mo @@ -256,22 +256,33 @@ module { /// ``` /// /// Runtime: `O(size)` - public func map(list : List, f : T -> R) : List = { - var blocks = VarArray.tabulate<[var ?R]>( - list.blocks.size(), - func(i) { - let db = list.blocks[i]; - VarArray.tabulate( - db.size(), - func(j) = switch (db[j]) { - case (?item) ?f(item); - case (null) null - } - ) - } - ); - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex + public func map(list : List, f : T -> R) : List { + let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); + let blocksCount = list.blocks.size(); + + var i = 1; + while (i < blocksCount) { + let oldBlock = list.blocks[i]; + let blockSize = oldBlock.size(); + let newBlock = VarArray.repeat(null, blockSize); + blocks[i] := newBlock; + var j = 0; + + while (j < blockSize) { + switch (oldBlock[j]) { + case (?item) newBlock[j] := ?f(item); + case (null) {} + }; + j += 1 + }; + i += 1 + }; + + { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + } }; /// Returns a new list containing only the elements from `list` for which the predicate returns true. @@ -1461,7 +1472,7 @@ module { if (e > 0) { switch (list.blocks[list.blockIndex][e - 1]) { case null Prim.trap(INTERNAL_ERROR); - case e return e; + case e return e } }; let b = list.blockIndex - 1 : Nat; From 36a170bc28d9775c85b21a0fa5d823ccbb5599eb Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 18 Jun 2025 22:08:55 +0300 Subject: [PATCH 032/123] Inline locate in put, getOpt. --- src/List.mo | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/List.mo b/src/List.mo index 5f5200711..a52421a05 100644 --- a/src/List.mo +++ b/src/List.mo @@ -267,7 +267,7 @@ module { let newBlock = VarArray.repeat(null, blockSize); blocks[i] := newBlock; var j = 0; - + while (j < blockSize) { switch (oldBlock[j]) { case (?item) newBlock[j] := ?f(item); @@ -604,12 +604,19 @@ module { /// /// Space: `O(1)` public func getOpt(list : List, index : Nat) : ?T { - let (a, b) = locate(index); + let (a, b) = do { + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); + let lz2 = lz >> 1; + if (lz & 1 == 0) { + (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) + } else { + (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) + } + }; if (a < list.blockIndex or list.elementIndex != 0 and a == list.blockIndex) { list.blocks[a][b] - } else { - null - } + } else null }; /// Overwrites the current element at `index` with `element`. @@ -625,7 +632,16 @@ module { /// /// Runtime: `O(1)` public func put(list : List, index : Nat, value : T) { - let (a, b) = locate(index); + let (a, b) = do { + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); + let lz2 = lz >> 1; + if (lz & 1 == 0) { + (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) + } else { + (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) + } + }; if (a < list.blockIndex or a == list.blockIndex and b < list.elementIndex) { list.blocks[a][b] := ?value } else Prim.trap "List index out of bounds in put" From 71df436fdd6047d2ce55255ef14554483e83b3c2 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 00:12:11 +0300 Subject: [PATCH 033/123] Try to optimize forEach. --- src/List.mo | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/List.mo b/src/List.mo index a52421a05..71377af03 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1577,27 +1577,19 @@ module { /// *Runtime and space assumes that `f` runs in O(1) time and space. public func forEach(list : List, f : T -> ()) { let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var sz = 0; - var db : [var ?T] = [var]; + var i = 1; + while (i < blocks) { + let db = list.blocks[i]; + let sz = db.size(); + if (sz == 0) return; - loop { - if (elementIndex == sz) { - blockIndex += 1; - if (blockIndex >= blocks) return; - db := list.blocks[blockIndex]; - sz := db.size(); - if (sz == 0) return; - elementIndex := 0 + var j = 0; + while (j < sz) { + let ?x = db[j] else Prim.trap(INTERNAL_ERROR); + f(x); + j += 1 }; - switch (db[elementIndex]) { - case (?x) { - f(x); - elementIndex += 1 - }; - case (_) return - } + i += 1 } }; From 75c7a3dd82957ba7c1e9a961c21e3f36ab466ef5 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 00:12:57 +0300 Subject: [PATCH 034/123] Fix. --- src/List.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index 71377af03..d6253e014 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1585,7 +1585,7 @@ module { var j = 0; while (j < sz) { - let ?x = db[j] else Prim.trap(INTERNAL_ERROR); + let ?x = db[j] else return; f(x); j += 1 }; From 8eba9a352cbe4f0b057e7e324657e2d56e71e3a6 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 00:16:48 +0300 Subject: [PATCH 035/123] Return switch in forEach. --- src/List.mo | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/List.mo b/src/List.mo index d6253e014..d36b80a94 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1585,9 +1585,13 @@ module { var j = 0; while (j < sz) { - let ?x = db[j] else return; - f(x); - j += 1 + switch(db[j]) { + case (?x) { + f(x); + j += 1 + }; + case null return; + }; }; i += 1 } From 066bf8c1dc635846e532d2f6151307f8e023b459 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 00:35:01 +0300 Subject: [PATCH 036/123] Optimize min, max. --- src/List.mo | 82 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/src/List.mo b/src/List.mo index d36b80a94..a2f6dceb4 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1576,22 +1576,24 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func forEach(list : List, f : T -> ()) { - let blocks = list.blocks.size(); + let blocks = list.blocks; + let blockCount = blocks.size(); + var i = 1; - while (i < blocks) { - let db = list.blocks[i]; + while (i < blockCount) { + let db = blocks[i]; let sz = db.size(); if (sz == 0) return; var j = 0; while (j < sz) { - switch(db[j]) { + switch (db[j]) { case (?x) { f(x); j += 1 }; - case null return; - }; + case null return + } }; i += 1 } @@ -1815,7 +1817,39 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func max(list : List, compare : (T, T) -> Order.Order) : ?T = minMax(list, compare, #greater); + public func max(list : List, compare : (T, T) -> Order.Order) : ?T { + var maxSoFar : T = switch (first(list)) { + case (?x) x; + case null return null + }; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return ?maxSoFar; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + switch (compare(x, maxSoFar)) { + case (#greater) maxSoFar := x; + case _ {} + } + }; + case null return ?maxSoFar + }; + j += 1 + }; + i += 1 + }; + + ?maxSoFar + }; /// Returns the least element in the list according to the ordering defined by `compare`. /// Returns `null` if the list is empty. @@ -1837,7 +1871,39 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func min(list : List, compare : (T, T) -> Order.Order) : ?T = minMax(list, compare, #less); + public func min(list : List, compare : (T, T) -> Order.Order) : ?T { + var minSoFar : T = switch (first(list)) { + case (?x) x; + case null return null + }; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return ?minSoFar; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + switch (compare(x, minSoFar)) { + case (#less) minSoFar := x; + case _ {} + } + }; + case null return ?minSoFar + }; + j += 1 + }; + i += 1 + }; + + ?minSoFar + }; /// Tests if two lists are equal by comparing their elements using the provided `equal` function. /// Returns true if and only if both lists have the same size and all corresponding elements From 06e494b4076d351488889ddd7b6cc186cf910409 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 01:14:56 +0300 Subject: [PATCH 037/123] Try optimize reverseForEach. --- src/List.mo | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/List.mo b/src/List.mo index a2f6dceb4..329a44623 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1721,25 +1721,30 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func reverseForEach(list : List, f : T -> ()) { - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; - - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 - }; - switch (db[elementIndex]) { + var i = list.blockIndex; + var j = list.elementIndex; + let db = list.blocks[i]; + while (j > 0) { + j -= 1; + switch (db[j]) { case (?x) f(x); case (_) Prim.trap(INTERNAL_ERROR) } + }; + + i -= 1; + + while (i > 0) { + let db = list.blocks[i]; + var j = db.size(); + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) f(x); + case (_) Prim.trap(INTERNAL_ERROR) + } + }; + i -= 1 } }; From 6ff32c0a6e383d3430dbc28822370639a0fe3dc5 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 01:18:45 +0300 Subject: [PATCH 038/123] Fix. --- src/List.mo | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/List.mo b/src/List.mo index 329a44623..0500c2bd7 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1721,19 +1721,7 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func reverseForEach(list : List, f : T -> ()) { - var i = list.blockIndex; - var j = list.elementIndex; - let db = list.blocks[i]; - while (j > 0) { - j -= 1; - switch (db[j]) { - case (?x) f(x); - case (_) Prim.trap(INTERNAL_ERROR) - } - }; - - i -= 1; - + var i = list.blocks.size() - 1 : Nat; while (i > 0) { let db = list.blocks[i]; var j = db.size(); @@ -1741,7 +1729,7 @@ module { j -= 1; switch (db[j]) { case (?x) f(x); - case (_) Prim.trap(INTERNAL_ERROR) + case (_) {} } }; i -= 1 From 0a3554ad5d97ce5ba443f4255438f709e72eba13 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 01:51:35 +0300 Subject: [PATCH 039/123] Strange error. --- src/List.mo | 469 +++++++++++++++++++++++----------------------------- 1 file changed, 211 insertions(+), 258 deletions(-) diff --git a/src/List.mo b/src/List.mo index 0500c2bd7..e29d51e0f 100644 --- a/src/List.mo +++ b/src/List.mo @@ -271,7 +271,11 @@ module { while (j < blockSize) { switch (oldBlock[j]) { case (?item) newBlock[j] := ?f(item); - case (null) {} + case null return { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + } }; j += 1 }; @@ -302,29 +306,29 @@ module { public func filter(list : List, predicate : T -> Bool) : List { let filtered = empty(); - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return filtered; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return filtered; - elementIndex := 0 + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return filtered; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + if (predicate(x)) add(filtered, x); + j += 1 + }; + case null return filtered + } }; - switch (db[elementIndex]) { - case (?x) { - if (predicate(x)) add(filtered, x); - elementIndex += 1 - }; - case (_) return filtered - } - } + i += 1 + }; + + filtered }; /// Returns a new list containing all elements from `list` for which the function returns ?element. @@ -345,32 +349,32 @@ module { public func filterMap(list : List, f : T -> ?R) : List { let filtered = empty(); - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return filtered; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return filtered; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - switch (f(x)) { - case (?y) add(filtered, y); - case null {} + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return filtered; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + switch (f(x)) { + case (?y) add(filtered, y); + case null {} + }; + j += 1 }; - elementIndex += 1 - }; - case (_) return filtered - } - } + case null return filtered + } + }; + i += 1 + }; + + filtered }; /// Returns the current number of elements in the list. @@ -1500,28 +1504,27 @@ module { list : List, f : (i : Nat, oldValue : T) -> (newValue : T) ) { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - var i = 0; + var index = 0; - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - db[elementIndex] := ?f(i, x); - elementIndex += 1 - }; - case (_) return + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + db[j] := ?f(index, x); + index += 1; + j += 1 + }; + case null return + } }; i += 1 } @@ -1531,28 +1534,26 @@ module { list : List, f : (oldValue : T) -> (newValue : T) ) { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return; - elementIndex := 0 + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + db[j] := ?f(x); + j += 1 + }; + case null return + } }; - switch (db[elementIndex]) { - case (?x) { - db[elementIndex] := ?f(x); - elementIndex += 1 - }; - case (_) return - } + i += 1 } }; @@ -1621,37 +1622,28 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func forEachEntry(list : List, f : (Nat, T) -> ()) { - /* Inlined version of - let o = object { - var i = 0; - public func fx(x : T) { f(i, x); i += 1; }; - }; - iterate(list, o.fx); - */ - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - var i = 0; + var index = 0; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return; - elementIndex := 0 + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + f(index, x); + j += 1; + index += 1 + }; + case null return + } }; - switch (db[elementIndex]) { - case (?x) { - f(i, x); - elementIndex += 1; - i += 1 - }; - case (_) return - } + i += 1 } }; @@ -1721,18 +1713,25 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func reverseForEach(list : List, f : T -> ()) { - var i = list.blocks.size() - 1 : Nat; - while (i > 0) { - let db = list.blocks[i]; - var j = db.size(); - while (j > 0) { - j -= 1; - switch (db[j]) { - case (?x) f(x); - case (_) {} - } + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var db : [var ?T] = if (blockIndex < list.blocks.size()) { + list.blocks[blockIndex] + } else { [var] }; + + loop { + if (elementIndex != 0) { + elementIndex -= 1 + } else { + blockIndex -= 1; + if (blockIndex == 0) return; + db := list.blocks[blockIndex]; + elementIndex := db.size() - 1 }; - i -= 1 + switch (db[elementIndex]) { + case (?x) f(x); + case (_) Prim.trap(INTERNAL_ERROR) + } } }; @@ -1760,36 +1759,6 @@ module { Option.isSome(indexOf(list, equal, element)) }; - private func minMax(list : List, compare : (T, T) -> Order.Order, compareResult : Order.Order) : ?T { - if (isEmpty(list)) return null; - - var extremum = get(list, 0); - - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return ?extremum; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return ?extremum; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - if (compare(x, extremum) == compareResult) extremum := x; - elementIndex += 1 - }; - case (_) return ?extremum - } - } - }; - /// Returns the greatest element in the list according to the ordering defined by `compare`. /// Returns `null` if the list is empty. /// @@ -1920,35 +1889,32 @@ module { /// /// *Runtime and space assumes that `equal` runs in O(1) time and space. public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool { - let size1 = size(list1); + if (size(list1) != size(list2)) return false; - if (size1 != size(list2)) return false; + let blocks1 = list1.blocks; + let blocks2 = list2.blocks; + let blockCount = Nat.min(blocks1.size(), blocks2.size()); - let blocks = Nat.min(list1.blocks.size(), list2.blocks.size()); - var blockIndex = 0; - var elementIndex = 0; - var sz = 0; - var db1 : [var ?T] = [var]; - var db2 : [var ?T] = [var]; + var i = 1; + while (i < blockCount) { + let db1 = blocks1[i]; + let db2 = blocks2[i]; + let sz = Nat.min(db1.size(), db2.size()); + if (sz == 0) return true; - loop { - if (elementIndex == sz) { - blockIndex += 1; - if (blockIndex >= blocks) return true; - db1 := list1.blocks[blockIndex]; - db2 := list2.blocks[blockIndex]; - sz := db1.size(); - if (sz == 0) return true; - elementIndex := 0 + var j = 0; + while (j < sz) { + switch (db1[j], db2[j]) { + case (?x, ?y) { + if (not equal(x, y)) return false; + j += 1 + }; + case (_, _) return true + } }; - switch (db1[elementIndex], db2[elementIndex]) { - case (?x, ?y) { - if (not equal(x, y)) return false; - elementIndex += 1 - }; - case (_) return true - } - } + i += 1 + }; + return true }; /// Compares two lists lexicographically using the provided `compare` function. @@ -1974,38 +1940,34 @@ module { /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order { - let size1 = size(list1); - let size2 = size(list2); + let blocks1 = list1.blocks; + let blocks2 = list2.blocks; + let blockCount = Nat.min(blocks1.size(), blocks2.size()); - let blocks = Nat.min(list1.blocks.size(), list2.blocks.size()); - var blockIndex = 0; - var elementIndex = 0; - var sz = 0; - var db1 : [var ?T] = [var]; - var db2 : [var ?T] = [var]; + var i = 1; + while (i < blockCount) { + let db1 = blocks1[i]; + let db2 = blocks2[i]; + let sz = Nat.min(db1.size(), db2.size()); + if (sz == 0) return Nat.compare(size(list1), size(list2)); - loop { - if (elementIndex == sz) { - blockIndex += 1; - if (blockIndex >= blocks) return Nat.compare(size1, size2); - db1 := list1.blocks[blockIndex]; - db2 := list2.blocks[blockIndex]; - sz := db1.size(); - if (sz == 0) return Nat.compare(size1, size2); - elementIndex := 0 - }; - switch (db1[elementIndex], db2[elementIndex]) { - case (?x, ?y) { - switch (compare(x, y)) { - case (#less) return #less; - case (#greater) return #greater; - case _ {} + var j = 0; + while (j < sz) { + switch (db1[j], db2[j]) { + case (?x, ?y) { + switch (compare(x, y)) { + case (#less) return #less; + case (#greater) return #greater; + case _ {} + }; + j += 1 }; - elementIndex += 1 - }; - case (_) return Nat.compare(size1, size2) - } - } + case (_, _) return Nat.compare(size(list1), size(list2)) + } + }; + i += 1 + }; + return Nat.compare(size(list1), size(list2)) }; /// Creates a textual representation of `list`, using `toText` to recursively @@ -2026,40 +1988,32 @@ module { /// /// *Runtime and space assumes that `toText` runs in O(1) time and space. public func toText(list : List, f : T -> Text) : Text { - let vsize = size(list); - if (vsize == 0) return "List[]"; + // avoid the trailing comma + var text = switch (first(list)) { + case (?x) f(x); + case null return "List[]" + }; - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var sz = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - var i = 1; - var text = ""; + var i = 2; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return "List[" # text # "]"; - while (i < vsize) { - if (elementIndex == sz) { - blockIndex += 1; - if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); - db := list.blocks[blockIndex]; - sz := db.size(); - if (sz == 0) Prim.trap(INTERNAL_ERROR); - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - text := text # f(x) # ", "; // Text implemented as rope - elementIndex += 1 + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) text := ", " # text # f(x); + case null return "List[" # text # "]" }; - case (_) Prim.trap(INTERNAL_ERROR) + j += 1 }; i += 1 }; - // avoid the trailing comma - text := text # f(Option.unwrap(last(list))); - "List[" # text # "]" }; @@ -2084,29 +2038,28 @@ module { public func foldLeft(list : List, base : A, combine : (A, T) -> A) : A { var accumulation = base; - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return accumulation; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return accumulation; - elementIndex := 0 + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return accumulation; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + accumulation := combine(accumulation, x); + j += 1 + }; + case null return accumulation + } }; - switch (db[elementIndex]) { - case (?x) { - accumulation := combine(accumulation, x); - elementIndex += 1 - }; - case (_) return accumulation - } - } + i += 1 + }; + accumulation }; /// Collapses the elements in `list` into a single value by starting with `base` From 4332bf2fa6e28f342abde41b828dfe676a51bb89 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 02:09:00 +0300 Subject: [PATCH 040/123] Switch to nested iteration. --- src/List.mo | 305 ++++++++++++++++++++++++---------------------------- 1 file changed, 139 insertions(+), 166 deletions(-) diff --git a/src/List.mo b/src/List.mo index e29d51e0f..da5283f93 100644 --- a/src/List.mo +++ b/src/List.mo @@ -318,12 +318,10 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - if (predicate(x)) add(filtered, x); - j += 1 - }; + case (?x) if (predicate(x)) add(filtered, x); case null return filtered - } + }; + j += 1 }; i += 1 }; @@ -361,15 +359,13 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - switch (f(x)) { - case (?y) add(filtered, y); - case null {} - }; - j += 1 + case (?x) switch (f(x)) { + case (?y) add(filtered, y); + case null {} }; case null return filtered - } + }; + j += 1 }; i += 1 }; @@ -698,31 +694,30 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var sz = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == sz) { - blockIndex += 1; - if (blockIndex >= blocks) return null; - db := list.blocks[blockIndex]; - sz := db.size(); - if (sz == 0) return null; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) if (equal(x, element)) return ?size({ - var blocks = [var]; - var blockIndex = blockIndex; - var elementIndex = elementIndex - }); - case (_) return null + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return null; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (equal(x, element)) return ?size({ + var blocks = [var]; + var blockIndex = i; + var elementIndex = j + }); + case null return null + }; + j += 1 }; - elementIndex += 1 - } + i += 1 + }; + null }; /// Finds the last index of `element` in `list` using equality of elements defined @@ -806,31 +801,30 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findIndex(list : List, predicate : T -> Bool) : ?Nat { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var sz = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == sz) { - blockIndex += 1; - if (blockIndex >= blocks) return null; - db := list.blocks[blockIndex]; - sz := db.size(); - if (sz == 0) return null; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) if (predicate(x)) return ?size({ - var blocks = [var]; - var blockIndex = blockIndex; - var elementIndex = elementIndex - }); - case (_) return null + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return null; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (predicate(x)) return ?size({ + var blocks = [var]; + var blockIndex = i; + var elementIndex = j + }); + case null return null + }; + j += 1 }; - elementIndex += 1 - } + i += 1 + }; + null }; /// Finds the index of the last element in `list` for which `predicate` is true. @@ -899,27 +893,26 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func all(list : List, predicate : T -> Bool) : Bool { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return true; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return true; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) if (not predicate(x)) return false; - case (_) return true + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return true; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (not predicate(x)) return false; + case null return true + }; + j += 1 }; - elementIndex += 1 - } + i += 1 + }; + true }; /// Returns true iff some element in `list` satisfies `predicate`. @@ -941,27 +934,26 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func any(list : List, predicate : T -> Bool) : Bool { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return false; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return false; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) if (predicate(x)) return true; - case (_) return false + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return false; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (predicate(x)) return true; + case null return false + }; + j += 1 }; - elementIndex += 1 - } + i += 1 + }; + false }; /// Returns an Iterator (`Iter`) over the elements of a List. @@ -1383,27 +1375,26 @@ module { let array = VarArray.repeat(Option.unwrap(first(list)), s); - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var sz = 0; - var db : [var ?T] = [var]; - var i = 0; + var index = 0; - loop { - if (elementIndex == sz) { - blockIndex += 1; - if (blockIndex >= blocks) return array; - db := list.blocks[blockIndex]; - sz := db.size(); - if (sz == 0) return array; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) array[i] := x; - case (_) return array + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return array; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) array[index] := x; + case null return array + }; + j += 1; + index += 1 }; - elementIndex += 1; i += 1 }; array @@ -1518,13 +1509,11 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - db[j] := ?f(index, x); - index += 1; - j += 1 - }; + case (?x) db[j] := ?f(index, x); case null return - } + }; + index += 1; + j += 1 }; i += 1 } @@ -1546,12 +1535,10 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - db[j] := ?f(x); - j += 1 - }; + case (?x) db[j] := ?f(x); case null return - } + }; + j += 1 }; i += 1 } @@ -1589,12 +1576,10 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - f(x); - j += 1 - }; + case (?x) f(x); case null return - } + }; + j += 1 }; i += 1 } @@ -1635,13 +1620,11 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - f(index, x); - j += 1; - index += 1 - }; + case (?x) f(index, x); case null return - } + }; + j += 1; + index += 1 }; i += 1 } @@ -1797,11 +1780,9 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - switch (compare(x, maxSoFar)) { - case (#greater) maxSoFar := x; - case _ {} - } + case (?x) switch (compare(x, maxSoFar)) { + case (#greater) maxSoFar := x; + case _ {} }; case null return ?maxSoFar }; @@ -1851,11 +1832,9 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - switch (compare(x, minSoFar)) { - case (#less) minSoFar := x; - case _ {} - } + case (?x) switch (compare(x, minSoFar)) { + case (#less) minSoFar := x; + case _ {} }; case null return ?minSoFar }; @@ -1905,12 +1884,10 @@ module { var j = 0; while (j < sz) { switch (db1[j], db2[j]) { - case (?x, ?y) { - if (not equal(x, y)) return false; - j += 1 - }; + case (?x, ?y) if (not equal(x, y)) return false; case (_, _) return true - } + }; + j += 1 }; i += 1 }; @@ -1954,16 +1931,14 @@ module { var j = 0; while (j < sz) { switch (db1[j], db2[j]) { - case (?x, ?y) { - switch (compare(x, y)) { - case (#less) return #less; - case (#greater) return #greater; - case _ {} - }; - j += 1 + case (?x, ?y) switch (compare(x, y)) { + case (#less) return #less; + case (#greater) return #greater; + case _ {} }; case (_, _) return Nat.compare(size(list1), size(list2)) - } + }; + j += 1 }; i += 1 }; @@ -2006,7 +1981,7 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) text := ", " # text # f(x); + case (?x) text := text # ", " # f(x); case null return "List[" # text # "]" }; j += 1 @@ -2050,12 +2025,10 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) { - accumulation := combine(accumulation, x); - j += 1 - }; + case (?x) accumulation := combine(accumulation, x); case null return accumulation - } + }; + j += 1 }; i += 1 }; From 2c8e1cca2f4e6bd55d5cf029e35a455d39f1e7c1 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 02:12:52 +0300 Subject: [PATCH 041/123] Add my nick name. --- src/List.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index da5283f93..1469536f0 100644 --- a/src/List.mo +++ b/src/List.mo @@ -5,7 +5,7 @@ /// This implementation is adapted with permission from the `vector` Mops package created by Research AG. /// /// Copyright: 2023 MR Research AG -/// Main author: Andrii Stepanov +/// Main author: Andrii Stepanov (AStepanov25) /// Contributors: Timo Hanke (timohanke), Andy Gura (andygura), react0r-com /// /// ```motoko name=import From c3df9663edab87c9063f43a2e84a976f5cdb1166 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 19 Jun 2025 16:35:54 +0300 Subject: [PATCH 042/123] Optimize last. --- src/List.mo | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/List.mo b/src/List.mo index 1469536f0..99921087f 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1480,15 +1480,13 @@ module { /// Space: `O(1)` public func last(list : List) : ?T { let e = list.elementIndex; - if (e > 0) { - switch (list.blocks[list.blockIndex][e - 1]) { - case null Prim.trap(INTERNAL_ERROR); - case e return e - } - }; + if (e > 0) return list.blocks[list.blockIndex][e - 1]; + let b = list.blockIndex - 1 : Nat; - let blocks = list.blocks; - if (b == 0) null else blocks[b][blocks[b].size() - 1] + if (b == 0) null else { + let block = list.blocks[b]; + block[block.size() - 1] + } }; public func forEachEntryChange( From 00444e60a990cbe099bbcffbc3dcdae97a84895e Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 23 Jun 2025 16:06:50 +0300 Subject: [PATCH 043/123] Refactor toVarArray. --- src/List.mo | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/List.mo b/src/List.mo index 99921087f..9249bab56 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1370,10 +1370,9 @@ module { /// /// Runtime: `O(size)` public func toVarArray(list : List) : [var T] { - let s = size(list); - if (s == 0) return [var]; + let ?fs = first(list) else return [var]; - let array = VarArray.repeat(Option.unwrap(first(list)), s); + let array = VarArray.repeat(fs, size(list)); var index = 0; From 3107c1ecf1f3d4b31a793674e893c3e291cfc945 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 23 Jun 2025 22:28:34 +0300 Subject: [PATCH 044/123] Added forEachRange. --- src/List.mo | 38 ++++++++++++++++++++++++++++++++++++++ test/List.test.mo | 16 ++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/List.mo b/src/List.mo index 9249bab56..310a640ac 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,6 +14,7 @@ import PureList "pure/List"; import Prim "mo:⛔"; +import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -1627,6 +1628,43 @@ module { } }; + public func forEachRange(list : List, f : T -> (), fromInclusive : Nat, toExclusive : Nat) { + if (not (fromInclusive <= toExclusive and toExclusive <= size(list))) Prim.trap("Invalid range"); + + func traverseBlock(block : [var ?T], f : T -> (), from : Nat, to : Nat) { + var i = from; + while (i < to) { + switch (block[i]) { + case (?value) f(value); + case null Prim.trap(INTERNAL_ERROR) + }; + i += 1 + } + }; + + let (fromBlock, fromElement) = locate(fromInclusive); + let (toBlock, toElement) = locate(toExclusive); + + let blocks = list.blocks; + let sz = blocks.size(); + + if (fromBlock == toBlock) { + if (fromBlock < sz) traverseBlock(blocks[fromBlock], f, fromElement, toElement); + return + }; + + traverseBlock(blocks[fromBlock], f, fromElement, blocks[fromBlock].size()); + + var i = fromBlock + 1; + let to = Nat.min(toBlock, sz); + while (i < to) { + traverseBlock(blocks[i], f, 0, blocks[i].size()); + i += 1 + }; + + if (toBlock < sz) traverseBlock(blocks[toBlock], f, 0, toElement) + }; + /// Like `forEachEntryRev` but iterates through the list in reverse order, /// from end to beginning. /// diff --git a/test/List.test.mo b/test/List.test.mo index 45c693f24..0c531cb4a 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1138,6 +1138,21 @@ func testToArray(n : Nat) : Bool { Array.equal(List.toArray(vec), Array.tabulate(n, func(i) = i), Nat.equal) }; +func testForEachRange(n : Nat) : Bool { + let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + + var sum = 0; + List.forEachRange(vec, func(x) = sum += x, 0, n); + + var checkSum = 0; + List.forEach(vec, func(x) = checkSum += x); + if (sum != checkSum) { + Debug.print("ForEachRange failed: expected " # Nat.toText(checkSum) # ", got " # Nat.toText(sum)); + return false + }; + true +}; + func testFromIter(n : Nat) : Bool { let iter = Nat.range(1, n + 1); let vec = List.fromIter(iter); @@ -1224,6 +1239,7 @@ func runAllTests() { runTest("testSort", testSort); runTest("testToArray", testToArray); runTest("testFromIter", testFromIter); + runTest("testForEachRange", testForEachRange); runTest("testFoldLeft", testFoldLeft); runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); From 7321df15feedb429cd1707958ba8958bee7ab16f Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 23 Jun 2025 23:27:09 +0300 Subject: [PATCH 045/123] Refactor toText --- src/List.mo | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/List.mo b/src/List.mo index 310a640ac..3c35efc7c 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1998,33 +1998,34 @@ module { /// /// *Runtime and space assumes that `toText` runs in O(1) time and space. public func toText(list : List, f : T -> Text) : Text { - // avoid the trailing comma - var text = switch (first(list)) { - case (?x) f(x); - case null return "List[]" - }; + func toTextInternal(list : List, f : T -> Text) : Text { + var text = switch (first(list)) { + case (?x) f(x); + case null return "" + }; - let blocks = list.blocks; - let blockCount = blocks.size(); + let blocks = list.blocks; + let blockCount = blocks.size(); - var i = 2; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return "List[" # text # "]"; + var i = 2; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return text; - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) text := text # ", " # f(x); - case null return "List[" # text # "]" + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) text := text # ", " # f(x); + case null return text + }; + j += 1 }; - j += 1 + i += 1 }; - i += 1 + text }; - - "List[" # text # "]" + "List[" # toTextInternal(list, f) # "]" }; /// Collapses the elements in `list` into a single value by starting with `base` From 059f3fa11278bb38f6cba27a6c861d8bc6bab161 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 24 Jun 2025 00:37:27 +0300 Subject: [PATCH 046/123] Optimize toPure, fromPure. --- src/List.mo | 40 ++++++++++++++++++++++++++++++++++------ test/List.test.mo | 22 +++++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/List.mo b/src/List.mo index 3c35efc7c..cc0ec4cf2 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,7 +14,6 @@ import PureList "pure/List"; import Prim "mo:⛔"; -import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -122,7 +121,28 @@ module { /// /// Space: `O(size)` public func toPure(list : List) : PureList.List { - PureList.fromIter(values(list)) // TODO: optimize + var result : PureList.List = null; + + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var db : [var ?T] = if (blockIndex < list.blocks.size()) { + list.blocks[blockIndex] + } else { [var] }; + + loop { + if (elementIndex != 0) { + elementIndex -= 1 + } else { + blockIndex -= 1; + if (blockIndex == 0) return result; + db := list.blocks[blockIndex]; + elementIndex := db.size() - 1 + }; + switch (db[elementIndex]) { + case (?x) result := ?(x, result); + case (_) Prim.trap(INTERNAL_ERROR) + } + } }; /// Converts a purely functional `List` to a mutable `List`. @@ -138,10 +158,18 @@ module { /// Runtime: `O(size)` /// /// Space: `O(size)` - public func fromPure(pure : PureList.List) : List { - let list = empty(); - PureList.forEach(pure, func(x) = add(list, x)); - list + public func fromPure(p : PureList.List) : List { + var pure = p; + var list = empty(); + loop { + switch (pure) { + case (?(x, xs)) { + add(list, x); + pure := xs + }; + case null return list + } + } }; private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { diff --git a/test/List.test.mo b/test/List.test.mo index a075a01c9..a6fcaed99 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -14,6 +14,7 @@ import Runtime "../src/Runtime"; import Int "../src/Int"; import Debug "../src/Debug"; import { Tuple2 } "../src/Tuples"; +import PureList "../src/pure/List"; let { run; test; suite } = Suite; @@ -1225,6 +1226,24 @@ func testFilterMap(n : Nat) : Bool { true }; +func testPure(n : Nat) : Bool { + let idArray = Array.tabulate(n, func(i) = i); + let vec = List.fromArray(idArray); + let pureList = List.toPure(vec); + let newVec = List.fromPure(pureList); + + if (not PureList.equal(pureList, PureList.fromArray(idArray), Nat.equal)) { + Debug.print("PureList conversion failed"); + return false + }; + if (not List.equal(newVec, vec, Nat.equal)) { + Debug.print("List conversion from PureList failed"); + return false + }; + + true +}; + // Run all tests func runAllTests() { runTest("testNew", testNew); @@ -1249,7 +1268,8 @@ func runAllTests() { runTest("testFoldLeft", testFoldLeft); runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); - runTest("testFilterMap", testFilterMap) + runTest("testFilterMap", testFilterMap); + runTest("testPure", testPure) }; // Run all tests From e90e70b2af231f011e4046cdc4651ef0b95269ac Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 24 Jun 2025 00:56:28 +0300 Subject: [PATCH 047/123] Refactor concatSlices --- src/List.mo | 121 +++++++++++++++------------------------------------- 1 file changed, 34 insertions(+), 87 deletions(-) diff --git a/src/List.mo b/src/List.mo index cc0ec4cf2..edb252dc4 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1007,7 +1007,31 @@ module { /// List, then this may lead to unexpected results. /// /// Runtime: `O(1)` - public func values(list : List) : Iter.Iter = values_(list); + public func values(list : List) : Iter.Iter = object { + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var db : [var ?T] = list.blocks[blockIndex]; + var dbSize = db.size(); + + public func next() : ?T { + if (elementIndex == dbSize) { + blockIndex += 1; + if (blockIndex >= blocks) return null; + db := list.blocks[blockIndex]; + dbSize := db.size(); + if (dbSize == 0) return null; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + elementIndex += 1; + return ?x + }; + case (_) return null + } + } + }; /// Returns an Iterator (`Iter`) over the items (index-value pairs) in the list. /// Each item is a tuple of `(index, value)`. The iterator provides a single method @@ -1255,84 +1279,6 @@ module { Array.tabulate(size(list), generator) }; - private func values_(list : List) : { - next : () -> ?T; - unsafeNext : () -> T; - nextSet : T -> () - } = valuesFrom(0, list); - - private func valuesFrom(start : Nat, list : List) : { - next : () -> ?T; - unsafeNext : () -> T; - nextSet : T -> () - } = object { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - if (start != 0) { - let (block, element) = locate(start - 1); - blockIndex := block; - elementIndex := element + 1 - }; - var db : [var ?T] = list.blocks[blockIndex]; - var dbSize = db.size(); - - public func next() : ?T { - if (elementIndex == dbSize) { - blockIndex += 1; - if (blockIndex >= blocks) return null; - db := list.blocks[blockIndex]; - dbSize := db.size(); - if (dbSize == 0) return null; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - elementIndex += 1; - return ?x - }; - case (_) return null - } - }; - - // version of next() without option type - // inlined version of - // public func unsafeNext() : T = { - // let ?x = next() else Prim.trap(INTERNAL_ERROR); - // x; - // }; - public func unsafeNext() : T { - if (elementIndex == dbSize) { - blockIndex += 1; - if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); - db := list.blocks[blockIndex]; - dbSize := db.size(); - if (dbSize == 0) Prim.trap(INTERNAL_ERROR); - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - elementIndex += 1; - return x - }; - case (_) Prim.trap(INTERNAL_ERROR) - } - }; - - public func nextSet(value : T) { - if (elementIndex == dbSize) { - blockIndex += 1; - if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); - db := list.blocks[blockIndex]; - dbSize := db.size(); - if (dbSize == 0) Prim.trap(INTERNAL_ERROR); - elementIndex := 0 - }; - db[elementIndex] := ?value; - elementIndex += 1 - } - }; - /// Creates a List containing elements from an Array. /// /// Example: @@ -2308,16 +2254,17 @@ module { }; var result = repeatInternal(null, length); - var resultIter = values_(result); + result.blockIndex := 1; + result.elementIndex := 0; + for (slice in slices.vals()) { let (list, start, end) = slice; - let values = valuesFrom(start, list); - var i = start; - while (i < end) { - let copiedValue = values.unsafeNext(); - resultIter.nextSet(copiedValue); - i += 1 - } + forEachRange( + list, + func(value) = add(result, value), + start, + end + ) }; result From 63c31d7fa22938c5116a40edbd8fd05fc4f8e7cc Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 24 Jun 2025 17:52:07 +0300 Subject: [PATCH 048/123] Optimize methods in List. --- src/List.mo | 1143 +++++++++++++++++++++++++++++---------------- test/List.test.mo | 22 +- 2 files changed, 757 insertions(+), 408 deletions(-) diff --git a/src/List.mo b/src/List.mo index c9ba834d7..a2e18cb4b 100644 --- a/src/List.mo +++ b/src/List.mo @@ -5,7 +5,7 @@ /// This implementation is adapted with permission from the `vector` Mops package created by Research AG. /// /// Copyright: 2023 MR Research AG -/// Main author: Andrii Stepanov +/// Main author: Andrii Stepanov (AStepanov25) /// Contributors: Timo Hanke (timohanke), Andy Gura (andygura), react0r-com /// /// ```motoko name=import @@ -60,46 +60,54 @@ module { /// Runtime: `O(1)` /// /// Space: `O(1)` - public func singleton(element : T) : List = repeat(element, 1); + public func singleton(element : T) : List = { + var blockIndex = 2; + var blocks = [var [var], [var ?element]]; + var elementIndex = 0 + }; - /// Creates a new List with `size` copies of the initial value. - /// - /// Example: - /// ```motoko include=import - /// let list = List.repeat(2, 4); - /// assert List.toArray(list) == [2, 2, 2, 2]; - /// ``` - /// - /// Runtime: `O(size)` - /// - /// Space: `O(size)` - public func repeat(initValue : T, size : Nat) : List { + private func repeatInternal(initValue : ?T, size : Nat) : List { let (blockIndex, elementIndex) = locate(size); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let data_blocks = VarArray.repeat<[var ?T]>([var], blocks); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); var i = 1; while (i < blockIndex) { - data_blocks[i] := VarArray.repeat(?initValue, data_block_size(i)); + dataBlocks[i] := VarArray.repeat(initValue, dataBlockSize(i)); i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - let block = VarArray.repeat(null, data_block_size(i)); - var j = 0; - while (j < elementIndex) { - block[j] := ?initValue; - j += 1 + let block = VarArray.repeat(null, dataBlockSize(i)); + if (not Option.isNull(initValue)) { + var j = 0; + while (j < elementIndex) { + block[j] := initValue; + j += 1 + } }; - data_blocks[i] := block + dataBlocks[i] := block }; { - var blocks = data_blocks; + var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex } }; + /// Creates a new List with `size` copies of the initial value. + /// + /// Example: + /// ```motoko include=import + /// let list = List.repeat(2, 4); + /// assert List.toArray(list) == [2, 2, 2, 2]; + /// ``` + /// + /// Runtime: `O(size)` + /// + /// Space: `O(size)` + public func repeat(initValue : T, size : Nat) : List = repeatInternal(?initValue, size); + /// Converts a mutable `List` to a purely functional `PureList`. /// /// Example: @@ -112,7 +120,28 @@ module { /// /// Space: `O(size)` public func toPure(list : List) : PureList.List { - PureList.fromIter(values(list)) // TODO: optimize + var result : PureList.List = null; + + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var db : [var ?T] = if (blockIndex < list.blocks.size()) { + list.blocks[blockIndex] + } else { [var] }; + + loop { + if (elementIndex != 0) { + elementIndex -= 1 + } else { + blockIndex -= 1; + if (blockIndex == 0) return result; + db := list.blocks[blockIndex]; + elementIndex := db.size() - 1 + }; + switch (db[elementIndex]) { + case (?x) result := ?(x, result); + case (_) Prim.trap(INTERNAL_ERROR) + } + } }; /// Converts a purely functional `List` to a mutable `List`. @@ -128,60 +157,60 @@ module { /// Runtime: `O(size)` /// /// Space: `O(size)` - public func fromPure(pure : PureList.List) : List { - let list = empty(); - PureList.forEach(pure, func(x) = add(list, x)); - list + public func fromPure(p : PureList.List) : List { + var pure = p; + var list = empty(); + loop { + switch (pure) { + case (?(x, xs)) { + add(list, x); + pure := xs + }; + case null return list + } + } }; - /// Add to list `count` copies of the initial value. - /// - /// ```motoko include=import - /// let list = List.repeat(2, 4); // [2, 2, 2, 2] - /// List.addRepeat(list, 2, 1); // [2, 2, 2, 2, 1, 1] - /// ``` - /// - /// The maximum number of elements in a `List` is 2^32. - /// - /// Runtime: `O(count)` - public func addRepeat(list : List, initValue : T, count : Nat) { + private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { let (blockIndex, elementIndex) = locate(size(list) + count); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let old_blocks = list.blocks.size(); - if (old_blocks < blocks) { - let old_data_blocks = list.blocks; + let oldBlocks = list.blocks.size(); + if (oldBlocks < blocks) { + let oldDataBlocks = list.blocks; list.blocks := VarArray.repeat<[var ?T]>([var], blocks); var i = 0; - while (i < old_blocks) { - list.blocks[i] := old_data_blocks[i]; + while (i < oldBlocks) { + list.blocks[i] := oldDataBlocks[i]; i += 1 } }; var cnt = count; while (cnt > 0) { - let db_size = data_block_size(list.blockIndex); - if (list.elementIndex == 0 and db_size <= cnt) { - list.blocks[list.blockIndex] := VarArray.repeat(?initValue, db_size); - cnt -= db_size; + let dbSize = dataBlockSize(list.blockIndex); + if (list.elementIndex == 0 and dbSize <= cnt) { + list.blocks[list.blockIndex] := VarArray.repeat(initValue, dbSize); + cnt -= dbSize; list.blockIndex += 1 } else { if (list.blocks[list.blockIndex].size() == 0) { - list.blocks[list.blockIndex] := VarArray.repeat(null, db_size) + list.blocks[list.blockIndex] := VarArray.repeat(null, dbSize) }; let from = list.elementIndex; - let to = Nat.min(list.elementIndex + cnt, db_size); + let to = Nat.min(list.elementIndex + cnt, dbSize); let block = list.blocks[list.blockIndex]; - var i = from; - while (i < to) { - block[i] := ?initValue; - i += 1 + if (not Option.isNull(initValue)) { + var i = from; + while (i < to) { + block[i] := initValue; + i += 1 + } }; list.elementIndex := to; - if (list.elementIndex == db_size) { + if (list.elementIndex == dbSize) { list.elementIndex := 0; list.blockIndex += 1 }; @@ -190,6 +219,18 @@ module { } }; + /// Add to list `count` copies of the initial value. + /// + /// ```motoko include=import + /// let list = List.repeat(2, 4); // [2, 2, 2, 2] + /// List.addRepeat(list, 2, 1); // [2, 2, 2, 2, 1, 1] + /// ``` + /// + /// The maximum number of elements in a `List` is 2^32. + /// + /// Runtime: `O(count)` + public func addRepeat(list : List, initValue : T, count : Nat) = addRepeatInternal(list, ?initValue, count); + /// Resets the list to size 0, de-referencing all elements. /// /// Example: @@ -224,10 +265,7 @@ module { public func clone(list : List) : List = { var blocks = VarArray.tabulate<[var ?T]>( list.blocks.size(), - func(i) = VarArray.tabulate( - list.blocks[i].size(), - func(j) = list.blocks[i][j] - ) + func(i) = VarArray.clone(list.blocks[i]) ); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex @@ -246,22 +284,37 @@ module { /// ``` /// /// Runtime: `O(size)` - public func map(list : List, f : T -> R) : List = { - var blocks = VarArray.tabulate<[var ?R]>( - list.blocks.size(), - func(i) { - let db = list.blocks[i]; - VarArray.tabulate( - db.size(), - func(j) = switch (db[j]) { - case (?item) ?f(item); - case (null) null + public func map(list : List, f : T -> R) : List { + let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); + let blocksCount = list.blocks.size(); + + var i = 1; + while (i < blocksCount) { + let oldBlock = list.blocks[i]; + let blockSize = oldBlock.size(); + let newBlock = VarArray.repeat(null, blockSize); + blocks[i] := newBlock; + var j = 0; + + while (j < blockSize) { + switch (oldBlock[j]) { + case (?item) newBlock[j] := ?f(item); + case null return { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex } - ) - } - ); - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex + }; + j += 1 + }; + i += 1 + }; + + { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + } }; /// Returns a new list containing only the elements from `list` for which the predicate returns true. @@ -280,12 +333,27 @@ module { /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func filter(list : List, predicate : T -> Bool) : List { let filtered = empty(); - forEach( - list, - func(x) { - if (predicate(x)) add(filtered, x) - } - ); + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return filtered; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (predicate(x)) add(filtered, x); + case null return filtered + }; + j += 1 + }; + i += 1 + }; + filtered }; @@ -306,15 +374,30 @@ module { /// *Runtime and space assumes that `f` runs in `O(1)` time and space. public func filterMap(list : List, f : T -> ?R) : List { let filtered = empty(); - forEach( - list, - func(x) { - switch (f(x)) { - case (?y) add(filtered, y); - case null {} - } - } - ); + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return filtered; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) switch (f(x)) { + case (?y) add(filtered, y); + case null {} + }; + case null return filtered + }; + j += 1 + }; + i += 1 + }; + filtered }; @@ -354,44 +437,44 @@ module { Nat32.toNat((d -% (1 <>> lz)) <>> lz +% i) }; - func data_block_size(blockIndex : Nat) : Nat { + func dataBlockSize(blockIndex : Nat) : Nat { // formula for the size of given blockIndex // don't call it for blockIndex == 0 Nat32.toNat(1 <>> Nat32.bitcountLeadingZero(Nat32.fromNat(blockIndex) / 3)) }; - func new_index_block_length(blockIndex : Nat32) : Nat { + func newIndexBlockLength(blockIndex : Nat32) : Nat { if (blockIndex <= 1) 2 else { let s = 30 - Nat32.bitcountLeadingZero(blockIndex); Nat32.toNat(((blockIndex >> s) +% 1) << s) } }; - func grow_index_block_if_needed(list : List) { + func growIndexBlockIfNeeded(list : List) { if (list.blocks.size() == list.blockIndex) { - let new_blocks = VarArray.repeat<[var ?T]>([var], new_index_block_length(Nat32.fromNat(list.blockIndex))); + let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Nat32.fromNat(list.blockIndex))); var i = 0; while (i < list.blockIndex) { - new_blocks[i] := list.blocks[i]; + newBlocks[i] := list.blocks[i]; i += 1 }; - list.blocks := new_blocks + list.blocks := newBlocks } }; - func shrink_index_block_if_needed(list : List) { + func shrinkIndexBlockIfNeeded(list : List) { let blockIndex = Nat32.fromNat(list.blockIndex); // kind of index of the first block in the super block if ((blockIndex << Nat32.bitcountLeadingZero(blockIndex)) << 2 == 0) { - let new_length = new_index_block_length(blockIndex); - if (new_length < list.blocks.size()) { - let new_blocks = VarArray.repeat<[var ?T]>([var], new_length); + let newLength = newIndexBlockLength(blockIndex); + if (newLength < list.blocks.size()) { + let newBlocks = VarArray.repeat<[var ?T]>([var], newLength); var i = 0; - while (i < new_length) { - new_blocks[i] := list.blocks[i]; + while (i < newLength) { + newBlocks[i] := list.blocks[i]; i += 1 }; - list.blocks := new_blocks + list.blocks := newBlocks } } }; @@ -416,24 +499,24 @@ module { public func add(list : List, element : T) { var elementIndex = list.elementIndex; if (elementIndex == 0) { - grow_index_block_if_needed(list); + growIndexBlockIfNeeded(list); let blockIndex = list.blockIndex; // When removing last we keep one more data block, so can be not empty if (list.blocks[blockIndex].size() == 0) { list.blocks[blockIndex] := VarArray.repeat( null, - data_block_size(blockIndex) + dataBlockSize(blockIndex) ) } }; - let last_data_block = list.blocks[list.blockIndex]; + let lastDataBlock = list.blocks[list.blockIndex]; - last_data_block[elementIndex] := ?element; + lastDataBlock[elementIndex] := ?element; elementIndex += 1; - if (elementIndex == last_data_block.size()) { + if (elementIndex == lastDataBlock.size()) { elementIndex := 0; list.blockIndex += 1 }; @@ -459,7 +542,7 @@ module { public func removeLast(list : List) : ?T { var elementIndex = list.elementIndex; if (elementIndex == 0) { - shrink_index_block_if_needed(list); + shrinkIndexBlockIfNeeded(list); var blockIndex = list.blockIndex; if (blockIndex == 1) { @@ -478,10 +561,10 @@ module { }; elementIndex -= 1; - var last_data_block = list.blocks[list.blockIndex]; + var lastDataBlock = list.blocks[list.blockIndex]; - let element = last_data_block[elementIndex]; - last_data_block[elementIndex] := null; + let element = lastDataBlock[elementIndex]; + lastDataBlock[elementIndex] := null; list.elementIndex := elementIndex; return element @@ -549,12 +632,19 @@ module { /// /// Space: `O(1)` public func getOpt(list : List, index : Nat) : ?T { - let (a, b) = locate(index); + let (a, b) = do { + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); + let lz2 = lz >> 1; + if (lz & 1 == 0) { + (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) + } else { + (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) + } + }; if (a < list.blockIndex or list.elementIndex != 0 and a == list.blockIndex) { list.blocks[a][b] - } else { - null - } + } else null }; /// Overwrites the current element at `index` with `element`. @@ -570,7 +660,16 @@ module { /// /// Runtime: `O(1)` public func put(list : List, index : Nat, value : T) { - let (a, b) = locate(index); + let (a, b) = do { + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); + let lz2 = lz >> 1; + if (lz & 1 == 0) { + (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) + } else { + (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) + } + }; if (a < list.blockIndex or a == list.blockIndex and b < list.elementIndex) { list.blocks[a][b] := ?value } else Prim.trap "List index out of bounds in put" @@ -597,10 +696,31 @@ module { /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func sort(list : List, compare : (T, T) -> Order.Order) { if (size(list) < 2) return; - let arr = toVarArray(list); - VarArray.sortInPlace(arr, compare); - for (i in arr.keys()) { - put(list, i, arr[i]) + let array = toVarArray(list); + + VarArray.sortInPlace(array, compare); + + var index = 0; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?_) db[j] := ?array[index]; + case _ return + }; + index += 1; + j += 1 + }; + i += 1 } }; @@ -625,8 +745,30 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - // inlining would save 10 instructions per entry - findIndex(list, func(x) = equal(element, x)) + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return null; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (equal(x, element)) return ?size({ + var blocks = [var]; + var blockIndex = i; + var elementIndex = j + }); + case null return null + }; + j += 1 + }; + i += 1 + }; + null }; /// Finds the last index of `element` in `list` using equality of elements defined @@ -646,8 +788,32 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - // inlining would save 10 instructions per entry - findLastIndex(list, func(x) = equal(element, x)) + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var db : [var ?T] = if (blockIndex < list.blocks.size()) { + list.blocks[blockIndex] + } else { [var] }; + + loop { + if (elementIndex != 0) { + elementIndex -= 1 + } else { + blockIndex -= 1; + if (blockIndex == 0) return null; + db := list.blocks[blockIndex]; + elementIndex := db.size() - 1 + }; + switch (db[elementIndex]) { + case (?x) { + if (equal(x, element)) return ?size({ + var blocks = [var]; + var blockIndex = blockIndex; + var elementIndex = elementIndex + }) + }; + case (_) Prim.trap(INTERNAL_ERROR) + } + } }; /// Returns the first value in `list` for which `predicate` returns true. @@ -664,12 +830,7 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func find(list : List, predicate : T -> Bool) : ?T { - for (element in values(list)) { - if (predicate element) { - return ?element - } - }; - null + Option.map(findIndex(list, predicate), func(i) = get(list, i)) }; /// Finds the index of the first element in `list` for which `predicate` is true. @@ -691,29 +852,30 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findIndex(list : List, predicate : T -> Bool) : ?Nat { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - var i = 0; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return null; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return null; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) if (predicate(x)) return ?i; - case (_) return null + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return null; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (predicate(x)) return ?size({ + var blocks = [var]; + var blockIndex = i; + var elementIndex = j + }); + case null return null + }; + j += 1 }; - elementIndex += 1; i += 1 - } + }; + null }; /// Finds the index of the last element in `list` for which `predicate` is true. @@ -735,7 +897,6 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findLastIndex(list : List, predicate : T -> Bool) : ?Nat { - var i = size(list); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex; var db : [var ?T] = if (blockIndex < list.blocks.size()) { @@ -743,20 +904,21 @@ module { } else { [var] }; loop { - if (blockIndex == 1) { - return null - }; - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; + if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; switch (db[elementIndex]) { case (?x) { - i -= 1; - if (predicate(x)) return ?i + if (predicate(x)) return ?size({ + var blocks = [var]; + var blockIndex = blockIndex; + var elementIndex = elementIndex + }) }; case (_) Prim.trap(INTERNAL_ERROR) } @@ -782,7 +944,26 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func all(list : List, predicate : T -> Bool) : Bool { - not any(list, func(x) : Bool = not predicate(x)) + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return true; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (not predicate(x)) return false; + case null return true + }; + j += 1 + }; + i += 1 + }; + true }; /// Returns true iff some element in `list` satisfies `predicate`. @@ -804,10 +985,26 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func any(list : List, predicate : T -> Bool) : Bool { - switch (findIndex(list, predicate)) { - case (null) false; - case (_) true - } + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return false; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (predicate(x)) return true; + case null return false + }; + j += 1 + }; + i += 1 + }; + false }; /// Returns an Iterator (`Iter`) over the elements of a List. @@ -832,7 +1029,31 @@ module { /// List, then this may lead to unexpected results. /// /// Runtime: `O(1)` - public func values(list : List) : Iter.Iter = values_(list); + public func values(list : List) : Iter.Iter = object { + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + var db : [var ?T] = list.blocks[blockIndex]; + var dbSize = db.size(); + + public func next() : ?T { + if (elementIndex == dbSize) { + blockIndex += 1; + if (blockIndex >= blocks) return null; + db := list.blocks[blockIndex]; + dbSize := db.size(); + if (dbSize == 0) return null; + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + elementIndex += 1; + return ?x + }; + case (_) return null + } + } + }; /// Returns an Iterator (`Iter`) over the items (index-value pairs) in the list. /// Each item is a tuple of `(index, value)`. The iterator provides a single method @@ -914,15 +1135,13 @@ module { } else { [var] }; public func next() : ?T { - if (blockIndex == 1) { - return null - }; - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; + if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; db[elementIndex] @@ -959,15 +1178,13 @@ module { } else { [var] }; public func next() : ?(Nat, T) { - if (blockIndex == 1) { - return null - }; - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; + if (blockIndex == 0) return null; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; switch (db[elementIndex]) { case (?x) { @@ -1056,50 +1273,20 @@ module { /// ``` /// /// Runtime: `O(size)` - public func toArray(list : List) : [T] = Array.tabulate(size(list), values_(list).unsafe_next_i); - - private func values_(list : List) : { - next : () -> ?T; - unsafe_next : () -> T; - unsafe_next_i : Nat -> T - } = object { + public func toArray(list : List) : [T] { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; - var db_size = 0; + var sz = 0; var db : [var ?T] = [var]; - public func next() : ?T { - if (elementIndex == db_size) { - blockIndex += 1; - if (blockIndex >= blocks) return null; - db := list.blocks[blockIndex]; - db_size := db.size(); - if (db_size == 0) return null; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - elementIndex += 1; - return ?x - }; - case (_) return null - } - }; - - // version of next() without option type - // inlined version of - // public func unsafe_next() : T = { - // let ?x = next() else Prim.trap(INTERNAL_ERROR); - // x; - // }; - public func unsafe_next() : T { - if (elementIndex == db_size) { + func generator(_ : Nat) : T { + if (elementIndex == sz) { blockIndex += 1; if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); db := list.blocks[blockIndex]; - db_size := db.size(); - if (db_size == 0) Prim.trap(INTERNAL_ERROR); + sz := db.size(); + if (sz == 0) Prim.trap(INTERNAL_ERROR); elementIndex := 0 }; switch (db[elementIndex]) { @@ -1111,26 +1298,7 @@ module { } }; - // version of next() without option type and throw-away argument - // inlined version of - // public func unsafe_next_(i : Nat) : T = unsafe_next(); - public func unsafe_next_i(i : Nat) : T { - if (elementIndex == db_size) { - blockIndex += 1; - if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); - db := list.blocks[blockIndex]; - db_size := db.size(); - if (db_size == 0) Prim.trap(INTERNAL_ERROR); - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - elementIndex += 1; - return x - }; - case (_) Prim.trap(INTERNAL_ERROR) - } - } + Array.tabulate(size(list), generator) }; /// Creates a List containing elements from an Array. @@ -1149,14 +1317,13 @@ module { public func fromArray(array : [T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let data_blocks = VarArray.repeat<[var ?T]>([var], blocks); - var i = 1; - var pos = 0; + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); - func make_block(len : Nat, fill : Nat) : [var ?T] { + func makeBlock(array : [T], p : Nat, len : Nat, fill : Nat) : [var ?T] { let block = VarArray.repeat(null, len); var j = 0; + var pos = p; while (j < fill) { block[j] := ?array[pos]; j += 1; @@ -1165,21 +1332,24 @@ module { block }; + var i = 1; + var pos = 0; + while (i < blockIndex) { - let len = data_block_size(i); - data_blocks[i] := make_block(len, len); + let len = dataBlockSize(i); + dataBlocks[i] := makeBlock(array, pos, len, len); + pos += len; i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - data_blocks[i] := make_block(data_block_size(i), elementIndex) + dataBlocks[i] := makeBlock(array, pos, dataBlockSize(i), elementIndex) }; { - var blocks = data_blocks; + var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex - }; - + } }; /// Creates a new mutable array containing all elements from the list. @@ -1197,16 +1367,33 @@ module { /// /// Runtime: `O(size)` public func toVarArray(list : List) : [var T] { - let s = size(list); - if (s == 0) return [var]; - let arr = VarArray.repeat(Option.unwrap(first(list)), s); - var i = 0; - let next = values_(list).unsafe_next; - while (i < s) { - arr[i] := next(); + let ?fs = first(list) else return [var]; + + let array = VarArray.repeat(fs, size(list)); + + var index = 0; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return array; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) array[index] := x; + case null return array + }; + j += 1; + index += 1 + }; i += 1 }; - arr + array }; /// Creates a new List containing all elements from the mutable array. @@ -1226,14 +1413,13 @@ module { public func fromVarArray(array : [var T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = new_index_block_length(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - let data_blocks = VarArray.repeat<[var ?T]>([var], blocks); - var i = 1; - var pos = 0; + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); - func make_block(len : Nat, fill : Nat) : [var ?T] { + func makeBlock(array : [var T], p : Nat, len : Nat, fill : Nat) : [var ?T] { let block = VarArray.repeat(null, len); var j = 0; + var pos = p; while (j < fill) { block[j] := ?array[pos]; j += 1; @@ -1242,21 +1428,24 @@ module { block }; + var i = 1; + var pos = 0; + while (i < blockIndex) { - let len = data_block_size(i); - data_blocks[i] := make_block(len, len); + let len = dataBlockSize(i); + dataBlocks[i] := makeBlock(array, pos, len, len); + pos += len; i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - data_blocks[i] := make_block(data_block_size(i), elementIndex) + dataBlocks[i] := makeBlock(array, pos, dataBlockSize(i), elementIndex) }; { - var blocks = data_blocks; + var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex - }; - + } }; /// Returns the first element of `list`, or `null` if the list is empty. @@ -1271,7 +1460,7 @@ module { /// /// Space: `O(1)` public func first(list : List) : ?T { - if (isEmpty(list)) null else list.blocks[1][0] + if (list.blockIndex == 1 and list.elementIndex == 0) null else list.blocks[1][0] }; /// Returns the last element of `list`. Traps if `list` is empty. @@ -1316,28 +1505,24 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func forEach(list : List, f : T -> ()) { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - f(x); - elementIndex += 1 + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) f(x); + case null return }; - case (_) return - } + j += 1 + }; + i += 1 } }; @@ -1363,37 +1548,26 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func forEachEntry(list : List, f : (Nat, T) -> ()) { - /* Inlined version of - let o = object { - var i = 0; - public func fx(x : T) { f(i, x); i += 1; }; - }; - iterate(list, o.fx); - */ - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - var i = 0; + var index = 0; + let blocks = list.blocks; + let blockCount = blocks.size(); - loop { - if (elementIndex == size) { - blockIndex += 1; - if (blockIndex >= blocks) return; - db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return; - elementIndex := 0 - }; - switch (db[elementIndex]) { - case (?x) { - f(i, x); - elementIndex += 1; - i += 1 + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) f(index, x); + case null return }; - case (_) return - } + j += 1; + index += 1 + }; + i += 1 } }; @@ -1427,15 +1601,13 @@ module { var i = size(list); loop { - if (blockIndex == 1) { - return - }; - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; + if (blockIndex == 0) return; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; i -= 1; switch (db[elementIndex]) { @@ -1472,15 +1644,13 @@ module { } else { [var] }; loop { - if (blockIndex == 1) { - return - }; - if (elementIndex == 0) { + if (elementIndex != 0) { + elementIndex -= 1 + } else { blockIndex -= 1; + if (blockIndex == 0) return; db := list.blocks[blockIndex]; elementIndex := db.size() - 1 - } else { - elementIndex -= 1 }; switch (db[elementIndex]) { case (?x) f(x); @@ -1534,18 +1704,35 @@ module { /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func max(list : List, compare : (T, T) -> Order.Order) : ?T { - if (isEmpty(list)) return null; - - var maxSoFar = get(list, 0); - forEach( - list, - func(x) = switch (compare(x, maxSoFar)) { - case (#greater) maxSoFar := x; - case _ {} - } - ); + var maxSoFar : T = switch (first(list)) { + case (?x) x; + case null return null + }; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return ?maxSoFar; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) switch (compare(x, maxSoFar)) { + case (#greater) maxSoFar := x; + case _ {} + }; + case null return ?maxSoFar + }; + j += 1 + }; + i += 1 + }; - return ?maxSoFar + ?maxSoFar }; /// Returns the least element in the list according to the ordering defined by `compare`. @@ -1569,18 +1756,35 @@ module { /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func min(list : List, compare : (T, T) -> Order.Order) : ?T { - if (isEmpty(list)) return null; - - var minSoFar = get(list, 0); - forEach( - list, - func(x) = switch (compare(x, minSoFar)) { - case (#less) minSoFar := x; - case _ {} - } - ); + var minSoFar : T = switch (first(list)) { + case (?x) x; + case null return null + }; + + let blocks = list.blocks; + let blockCount = blocks.size(); - return ?minSoFar + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return ?minSoFar; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) switch (compare(x, minSoFar)) { + case (#less) minSoFar := x; + case _ {} + }; + case null return ?minSoFar + }; + j += 1 + }; + i += 1 + }; + + ?minSoFar }; /// Tests if two lists are equal by comparing their elements using the provided `equal` function. @@ -1605,18 +1809,29 @@ module { /// /// *Runtime and space assumes that `equal` runs in O(1) time and space. public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool { - let size1 = size(list1); + if (size(list1) != size(list2)) return false; - if (size1 != size(list2)) return false; + let blocks1 = list1.blocks; + let blocks2 = list2.blocks; + let blockCount = Nat.min(blocks1.size(), blocks2.size()); - let next1 = values_(list1).unsafe_next; - let next2 = values_(list2).unsafe_next; - var i = 0; - while (i < size1) { - if (not equal(next1(), next2())) return false; + var i = 1; + while (i < blockCount) { + let db1 = blocks1[i]; + let db2 = blocks2[i]; + let sz = Nat.min(db1.size(), db2.size()); + if (sz == 0) return true; + + var j = 0; + while (j < sz) { + switch (db1[j], db2[j]) { + case (?x, ?y) if (not equal(x, y)) return false; + case (_, _) return true + }; + j += 1 + }; i += 1 }; - return true }; @@ -1643,22 +1858,32 @@ module { /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order { - let size1 = size(list1); - let size2 = size(list2); - let minSize = if (size1 < size2) { size1 } else { size2 }; + let blocks1 = list1.blocks; + let blocks2 = list2.blocks; + let blockCount = Nat.min(blocks1.size(), blocks2.size()); - let next1 = values_(list1).unsafe_next; - let next2 = values_(list2).unsafe_next; - var i = 0; - while (i < minSize) { - switch (compare(next1(), next2())) { - case (#less) return #less; - case (#greater) return #greater; - case _ {} + var i = 1; + while (i < blockCount) { + let db1 = blocks1[i]; + let db2 = blocks2[i]; + let sz = Nat.min(db1.size(), db2.size()); + if (sz == 0) return Nat.compare(size(list1), size(list2)); + + var j = 0; + while (j < sz) { + switch (db1[j], db2[j]) { + case (?x, ?y) switch (compare(x, y)) { + case (#less) return #less; + case (#greater) return #greater; + case _ {} + }; + case (_, _) return Nat.compare(size(list1), size(list2)) + }; + j += 1 }; i += 1 }; - Nat.compare(size1, size2) + return Nat.compare(size(list1), size(list2)) }; /// Creates a textual representation of `list`, using `toText` to recursively @@ -1679,20 +1904,34 @@ module { /// /// *Runtime and space assumes that `toText` runs in O(1) time and space. public func toText(list : List, f : T -> Text) : Text { - let vsize : Int = size(list); - let next = values_(list).unsafe_next; - var i = 0; - var text = ""; - while (i < vsize - 1) { - text := text # f(next()) # ", "; // Text implemented as rope - i += 1 - }; - if (vsize > 0) { - // avoid the trailing comma - text := text # f(get(list, i)) - }; + func toTextInternal(list : List, f : T -> Text) : Text { + var text = switch (first(list)) { + case (?x) f(x); + case null return "" + }; - "List[" # text # "]" + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 2; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return text; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) text := text # ", " # f(x); + case null return text + }; + j += 1 + }; + i += 1 + }; + text + }; + "List[" # toTextInternal(list, f) # "]" }; /// Collapses the elements in `list` into a single value by starting with `base` @@ -1716,11 +1955,25 @@ module { public func foldLeft(list : List, base : A, combine : (A, T) -> A) : A { var accumulation = base; - forEach( - list, - func(x) = accumulation := combine(accumulation, x) - ); + let blocks = list.blocks; + let blockCount = blocks.size(); + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return accumulation; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) accumulation := combine(accumulation, x); + case null return accumulation + }; + j += 1 + }; + i += 1 + }; accumulation }; @@ -1745,12 +1998,26 @@ module { public func foldRight(list : List, base : A, combine : (T, A) -> A) : A { var accumulation = base; - reverseForEach( - list, - func(x) = accumulation := combine(x, accumulation) - ); + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var db : [var ?T] = if (blockIndex < list.blocks.size()) { + list.blocks[blockIndex] + } else { [var] }; - accumulation + loop { + if (elementIndex != 0) { + elementIndex -= 1 + } else { + blockIndex -= 1; + if (blockIndex == 0) return accumulation; + db := list.blocks[blockIndex]; + elementIndex := db.size() - 1 + }; + switch (db[elementIndex]) { + case (?x) accumulation := combine(x, accumulation); + case (_) Prim.trap(INTERNAL_ERROR) + } + } }; /// Reverses the order of elements in `list` by overwriting in place. @@ -1771,17 +2038,49 @@ module { /// Space: `O(1)` public func reverseInPlace(list : List) { let vsize = size(list); - if (vsize == 0) return; + if (vsize <= 1) return; + let count = vsize / 2; var i = 0; - var j = vsize - 1 : Nat; - var temp = get(list, 0); - while (i < vsize / 2) { - temp := get(list, j); - put(list, j, get(list, i)); - put(list, i, temp); - i += 1; - j -= 1 + + let blocks = list.blocks.size(); + var blockIndexFront = 0; + var elementIndexFront = 0; + var sz = 0; + var dbFront : [var ?T] = [var]; + + var blockIndexBack = list.blockIndex; + var elementIndexBack = list.elementIndex; + var dbBack : [var ?T] = if (blockIndexBack < list.blocks.size()) { + list.blocks[blockIndexBack] + } else { [var] }; + + while (i < count) { + if (elementIndexFront == sz) { + blockIndexFront += 1; + if (blockIndexFront >= blocks) return; + dbFront := list.blocks[blockIndexFront]; + sz := dbFront.size(); + if (sz == 0) return; + elementIndexFront := 0 + }; + + if (elementIndexBack == 0) { + blockIndexBack -= 1; + if (blockIndexBack == 0) return; + dbBack := list.blocks[blockIndexBack]; + elementIndexBack := dbBack.size() - 1 + } else { + elementIndexBack -= 1 + }; + + let temp = dbFront[elementIndexFront]; + dbFront[elementIndexFront] := dbBack[elementIndexBack]; + dbBack[elementIndexBack] := temp; + + elementIndexFront += 1; + + i += 1 } }; @@ -1802,14 +2101,43 @@ module { /// /// Space: `O(1)` public func reverse(list : List) : List { - let rlist = empty(); + let rlist = repeatInternal(null, size(list)); - reverseForEach( - list, - func(x) = add(rlist, x) - ); + let blocks = list.blocks.size(); + var blockIndexFront = 0; + var elementIndexFront = 0; + var sz = 0; + var dbFront : [var ?T] = [var]; + + var blockIndexBack = rlist.blockIndex; + var elementIndexBack = rlist.elementIndex; + var dbBack : [var ?T] = if (blockIndexBack < rlist.blocks.size()) { + rlist.blocks[blockIndexBack] + } else { [var] }; + + loop { + if (elementIndexFront == sz) { + blockIndexFront += 1; + if (blockIndexFront >= blocks) return rlist; + dbFront := list.blocks[blockIndexFront]; + sz := dbFront.size(); + if (sz == 0) return rlist; + elementIndexFront := 0 + }; - rlist + if (elementIndexBack == 0) { + blockIndexBack -= 1; + if (blockIndexBack == 0) return rlist; + dbBack := rlist.blocks[blockIndexBack]; + elementIndexBack := dbBack.size() - 1 + } else { + elementIndexBack -= 1 + }; + + dbBack[elementIndexBack] := dbFront[elementIndexFront]; + + elementIndexFront += 1 + } }; /// Returns true if and only if the list is empty. @@ -1826,5 +2154,6 @@ module { /// Space: `O(1)` public func isEmpty(list : List) : Bool { list.blockIndex == 1 and list.elementIndex == 0 - } + }; + } diff --git a/test/List.test.mo b/test/List.test.mo index fcf0f4828..090e8d8e8 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -14,6 +14,7 @@ import Runtime "../src/Runtime"; import Int "../src/Int"; import Debug "../src/Debug"; import { Tuple2 } "../src/Tuples"; +import PureList "../src/pure/List"; let { run; test; suite } = Suite; @@ -1210,6 +1211,24 @@ func testFilterMap(n : Nat) : Bool { true }; +func testPure(n : Nat) : Bool { + let idArray = Array.tabulate(n, func(i) = i); + let vec = List.fromArray(idArray); + let pureList = List.toPure(vec); + let newVec = List.fromPure(pureList); + + if (not PureList.equal(pureList, PureList.fromArray(idArray), Nat.equal)) { + Debug.print("PureList conversion failed"); + return false + }; + if (not List.equal(newVec, vec, Nat.equal)) { + Debug.print("List conversion from PureList failed"); + return false + }; + + true +}; + // Run all tests func runAllTests() { runTest("testNew", testNew); @@ -1233,7 +1252,8 @@ func runAllTests() { runTest("testFoldLeft", testFoldLeft); runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); - runTest("testFilterMap", testFilterMap) + runTest("testFilterMap", testFilterMap); + runTest("testPure", testPure) }; // Run all tests From 55376d8ca483d78efa654789ac4ad698b408968c Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 24 Jun 2025 18:07:07 +0300 Subject: [PATCH 049/123] Update changelog, rename variable. --- Changelog.md | 3 ++- src/List.mo | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Changelog.md b/Changelog.md index 331408bef..db263866c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,7 +3,8 @@ ## Next * **Breaking:** Enable persistence of `Random` and `AsyncRandom` state in stable memory (#329). -* Fix a bug in `List.last` (#336). +* Fix a bug in `List.last` (#336). +* Optimize methods in `List` (#337). ## 0.5.0 diff --git a/src/List.mo b/src/List.mo index a2e18cb4b..5c69b2faf 100644 --- a/src/List.mo +++ b/src/List.mo @@ -157,14 +157,14 @@ module { /// Runtime: `O(size)` /// /// Space: `O(size)` - public func fromPure(p : PureList.List) : List { - var pure = p; + public func fromPure(pure : PureList.List) : List { + var p = pure; var list = empty(); loop { - switch (pure) { + switch (p) { case (?(x, xs)) { add(list, x); - pure := xs + p := xs }; case null return list } From c6bbb1c628420381a5bd44211f580658e55616a9 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 25 Jun 2025 21:19:29 +0300 Subject: [PATCH 050/123] Change size singature. --- src/List.mo | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/List.mo b/src/List.mo index 5c69b2faf..a18bf83f2 100644 --- a/src/List.mo +++ b/src/List.mo @@ -410,7 +410,12 @@ module { /// ``` /// /// Runtime: `O(1)` (with some internal calculations) - public func size(list : List) : Nat { + public func size( + list : { + var blockIndex : Nat; + var elementIndex : Nat + } + ) : Nat { let d = Nat32.fromNat(list.blockIndex); let i = Nat32.fromNat(list.elementIndex); @@ -757,8 +762,7 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) if (equal(x, element)) return ?size({ - var blocks = [var]; + case (?x) if (equal(x, element)) return ?size({ var blockIndex = i; var elementIndex = j }); @@ -805,8 +809,7 @@ module { }; switch (db[elementIndex]) { case (?x) { - if (equal(x, element)) return ?size({ - var blocks = [var]; + if (equal(x, element)) return ?size({ var blockIndex = blockIndex; var elementIndex = elementIndex }) @@ -864,8 +867,7 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) if (predicate(x)) return ?size({ - var blocks = [var]; + case (?x) if (predicate(x)) return ?size({ var blockIndex = i; var elementIndex = j }); @@ -914,8 +916,7 @@ module { }; switch (db[elementIndex]) { case (?x) { - if (predicate(x)) return ?size({ - var blocks = [var]; + if (predicate(x)) return ?size({ var blockIndex = blockIndex; var elementIndex = elementIndex }) From 4986a65ce0e943aeb2fdc7998f28c4523b6e2324 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 28 Jun 2025 19:40:41 +0300 Subject: [PATCH 051/123] Added functions. --- src/List.mo | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) diff --git a/src/List.mo b/src/List.mo index edb252dc4..9523592ea 100644 --- a/src/List.mo +++ b/src/List.mo @@ -251,6 +251,59 @@ module { list.elementIndex := 0 }; + /// Creates a list of size `size`. Each element at index i + /// is created by applying `generator` to i. + /// + /// ```motoko include=import + /// import Nat "mo:core/Nat"; + /// + /// let list = List.tabulate(4, func i = i * 2); + /// assert List.toArray(list) == [0, 2, 4, 6]; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `generator` runs in O(1) time and space. + public func tabulate(size : Nat, generator : Nat -> T) : List { + let (blockIndex, elementIndex) = locate(size); + + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); + + func makeBlock(generator : Nat -> T, p : Nat, len : Nat, fill : Nat) : [var ?T] { + let block = VarArray.repeat(null, len); + var j = 0; + var pos = p; + while (j < fill) { + block[j] := ?generator(pos); + j += 1; + pos += 1 + }; + block + }; + + var i = 1; + var pos = 0; + + while (i < blockIndex) { + let len = dataBlockSize(i); + dataBlocks[i] := makeBlock(generator, pos, len, len); + pos += len; + i += 1 + }; + if (elementIndex != 0 and blockIndex < blocks) { + dataBlocks[i] := makeBlock(generator, pos, dataBlockSize(i), elementIndex) + }; + + { + var blocks = dataBlocks; + var blockIndex = blockIndex; + var elementIndex = elementIndex + } + }; + /// Returns a copy of a List, with the same size. /// /// Example: @@ -318,6 +371,97 @@ module { } }; + /// Applies `f` to each element of `list` in place, + /// retaining the original ordering of elements. + /// This modifies the original list. + /// + /// ```motoko include=import + /// import Nat "mo:core/Nat"; + /// + /// let list = List.fromArray([0, 1, 2, 3]); + /// List.mapInPlace(list, func x = x * 3); + /// assert List.equal(list, List.fromArray([0, 3, 6, 9]), Nat.equal); + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapInPlace(list : List, f : T -> T) { + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) db[j] := ?f(x); + case null return + }; + j += 1 + }; + i += 1 + } + }; + + /// Creates a new list by applying `f` to each element in `list` and its index. + /// Retains original ordering of elements. + /// + /// ```motoko include=import + /// import Nat "mo:core/Nat"; + /// + /// let list = List.fromArray([10, 10, 10, 10]); + /// let newList = List.mapEntries(list, func (x, i) = i * x); + /// assert List.equal(newList, List.fromArray([0, 10, 20, 30]), Nat.equal); + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapEntries(list : List, f : (T, Nat) -> R) : List { + let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); + let blocksCount = list.blocks.size(); + + var index = 0; + + var i = 1; + while (i < blocksCount) { + let oldBlock = list.blocks[i]; + let blockSize = oldBlock.size(); + let newBlock = VarArray.repeat(null, blockSize); + blocks[i] := newBlock; + var j = 0; + + while (j < blockSize) { + switch (oldBlock[j]) { + case (?item) newBlock[j] := ?f(item, index); + case null return { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + } + }; + j += 1; + index += 1 + }; + i += 1 + }; + + { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + } + }; + /// Returns a new list containing only the elements from `list` for which the predicate returns true. /// /// Example: @@ -1602,6 +1746,82 @@ module { } }; + /// Returns an iterator over a slice of `list` starting at `fromInclusive` up to (but not including) `toExclusive`. + /// + /// Negative indices are relative to the end of the list. For example, `-1` corresponds to the last element in the list. + /// + /// If the indices are out of bounds, they are clamped to the list bounds. + /// If the first index is greater than the second, the function returns an empty iterator. + /// + /// ```motoko include=import + /// let list = List.fromArray([1, 2, 3, 4, 5]); + /// let iter1 = List.range(list, 3, list.size()); + /// assert iter1.next() == ?4; + /// assert iter1.next() == ?5; + /// assert iter1.next() == null; + /// + /// let iter2 = List.range(list, 3, -1); + /// assert iter2.next() == ?4; + /// assert iter2.next() == null; + /// + /// let iter3 = List.range(list, 0, 0); + /// assert iter3.next() == null; + /// ``` + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func range(list : List, fromInclusive : Int, toExclusive : Int) : Iter.Iter { + let sz = size(list); + // Convert negative indices to positive and handle bounds + let startInt = if (fromInclusive < 0) { + let s = sz + fromInclusive; + if (s < 0) { 0 } else { s } + } else { + if (fromInclusive > sz) { sz } else { fromInclusive } + }; + let endInt = if (toExclusive < 0) { + let e = sz + toExclusive; + if (e < 0) { 0 } else { e } + } else { + if (toExclusive > sz) { sz } else { toExclusive } + }; + // Convert to Nat (values are non-negative due to bounds checking above) + let start = Prim.abs(startInt); + let end = Prim.abs(endInt); + + object { + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + if (start != 0) { + let (block, element) = locate(start - 1); + blockIndex := block; + elementIndex := element + 1 + }; + var db : [var ?T] = list.blocks[blockIndex]; + var dbSize = db.size(); + var index = 0; + + public func next() : ?T { + if (index >= end) return null; + index += 1; + + if (elementIndex == dbSize) { + blockIndex += 1; + if (blockIndex >= blocks) return null; + db := list.blocks[blockIndex]; + dbSize := db.size(); + if (dbSize == 0) return null; + elementIndex := 0 + }; + let ret = db[elementIndex]; + elementIndex += 1; + ret + } + } + }; + public func forEachRange(list : List, f : T -> (), fromInclusive : Nat, toExclusive : Nat) { if (not (fromInclusive <= toExclusive and toExclusive <= size(list))) Prim.trap("Invalid range"); From db8afeddcc6ccf0c2c6c49640b33120c949029fb Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 28 Jun 2025 19:50:26 +0300 Subject: [PATCH 052/123] Add flatten. --- src/List.mo | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/List.mo b/src/List.mo index 9523592ea..8c9ddd86d 100644 --- a/src/List.mo +++ b/src/List.mo @@ -304,6 +304,44 @@ module { } }; + /// Combines a list of lists into a single list. Retains the original + /// ordering of the elements. + /// + /// ```motoko include=import + /// import Nat "mo:core/Nat"; + /// + /// let lists = List.fromArray([ + /// List.fromArray([0, 1, 2]), List.fromArray([2, 3]), List.fromArray([]), List.fromArray([4]) + /// ]); + /// let flatList = List.flatten(lists); + /// assert List.equal(flatList, List.fromArray([0, 1, 2, 2, 3, 4]), Nat.equal); + /// ``` + /// + /// Runtime: O(number of elements in list) + /// + /// Space: O(number of elements in list) + public func flatten(lists : List>) : List { + var sz = 0; + forEach>(lists, func(sublist) = sz += size(sublist)); + + let result = repeatInternal(null, sz); + result.blockIndex := 1; + result.elementIndex := 0; + + forEach>( + lists, + func(sublist) { + forEach( + sublist, + func(item) { + add(result, item) + } + ) + } + ); + result + }; + /// Returns a copy of a List, with the same size. /// /// Example: From 0cf48e0fcefe92fa74545759b5dc91647237406e Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 28 Jun 2025 22:05:51 +0300 Subject: [PATCH 053/123] Added functions. --- src/List.mo | 285 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 240 insertions(+), 45 deletions(-) diff --git a/src/List.mo b/src/List.mo index 8c9ddd86d..20635f497 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,6 +14,7 @@ import PureList "pure/List"; import Prim "mo:⛔"; +import Result "Result"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -307,6 +308,8 @@ module { /// Combines a list of lists into a single list. Retains the original /// ordering of the elements. /// + /// This has better performance compared to `List.join()`. + /// /// ```motoko include=import /// import Nat "mo:core/Nat"; /// @@ -342,6 +345,38 @@ module { result }; + /// Combines an iterator of lists into a single list. + /// Retains the original ordering of the elements. + /// + /// Consider using `List.flatten()` for better performance. + /// + /// ```motoko include=import + /// import Nat "mo:core/Nat"; + /// + /// let lists = [List.fromArray([0, 1, 2]), List.fromArray([2, 3]), List.fromArray([]), List.fromArray([4])]; + /// let joinedList = List.join(lists.vals()); + /// assert List.equal(joinedList, List.fromArray([0, 1, 2, 2, 3, 4]), Nat.equal); + /// ``` + /// + /// Runtime: O(number of elements in list) + /// + /// Space: O(number of elements in list) + public func join(lists : Iter.Iter>) : List { + var result = empty(); + for (list in lists) { + let oldBlockIndex = result.blockIndex; + let oldElementIndex = result.elementIndex; + + addRepeatInternal(result, null, size(list)); + + result.blockIndex := oldBlockIndex; + result.elementIndex := oldElementIndex; + + forEach(list, func item = add(result, item)); + }; + result + }; + /// Returns a copy of a List, with the same size. /// /// Example: @@ -500,6 +535,80 @@ module { } }; + /// Creates a new list by applying `f` to each element in `list`. + /// If any invocation of `f` produces an `#err`, returns an `#err`. Otherwise + /// returns an `#ok` containing the new list. + /// + /// ```motoko include=import + /// import Result "mo:core/Result"; + /// + /// let list = List.fromArray([4, 3, 2, 1, 0]); + /// // divide 100 by every element in the list + /// let result = List.mapResult(list, func x { + /// if (x > 0) { + /// #ok(100 / x) + /// } else { + /// #err "Cannot divide by zero" + /// } + /// }); + /// assert Result.isErr(result); + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E> { + var error : ?E = null; + + let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); + let blocksCount = list.blocks.size(); + + var i = 1; + while (i < blocksCount) { + let oldBlock = list.blocks[i]; + let blockSize = oldBlock.size(); + let newBlock = VarArray.repeat(null, blockSize); + blocks[i] := newBlock; + var j = 0; + + while (j < blockSize) { + switch (oldBlock[j]) { + case (?item) newBlock[j] := switch (f(item)) { + case (#ok x) ?x; + case (#err e) switch (error) { + case (null) { + error := ?e; + null + }; + case (?_) null + } + }; + case null return switch (error) { + case (null) return #ok { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + }; + case (?e) return #err e + } + }; + j += 1 + }; + i += 1 + }; + + switch (error) { + case (null) return #ok { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + }; + case (?e) return #err e + } + }; + /// Returns a new list containing only the elements from `list` for which the predicate returns true. /// /// Example: @@ -584,6 +693,46 @@ module { filtered }; + /// Creates a new list by applying `k` to each element in `list`, + /// and concatenating the resulting iterators in order. + /// + /// ```motoko include=import + /// import Int "mo:core/Int" + /// + /// let list = List.fromArray([1, 2, 3, 4]); + /// let newList = List.flatMap(list, func x = [x, -x].vals()); + /// assert List.equal(newList, List.fromArray([1, -1, 2, -2, 3, -3, 4, -4]), Int.equal); + /// ``` + /// Runtime: O(size) + /// + /// Space: O(size) + /// *Runtime and space assumes that `k` runs in O(1) time and space. + public func flatMap(list : List, k : T -> Iter.Iter) : List { + let result = empty(); + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return result; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) for (y in k(x)) add(result, y); + case _ return result + }; + j += 1 + }; + i += 1 + }; + + result + }; + /// Returns the current number of elements in the list. /// /// Example: @@ -1784,6 +1933,22 @@ module { } }; + func actualInterval(fromInclusive : Int, toExclusive : Int, size : Nat) : (Nat, Nat) { + let startInt = if (fromInclusive < 0) { + let s = size + fromInclusive; + if (s < 0) { 0 } else { s } + } else { + if (fromInclusive > size) { size } else { fromInclusive } + }; + let endInt = if (toExclusive < 0) { + let e = size + toExclusive; + if (e < 0) { 0 } else { e } + } else { + if (toExclusive > size) { size } else { toExclusive } + }; + (Prim.abs(startInt), Prim.abs(endInt)) + }; + /// Returns an iterator over a slice of `list` starting at `fromInclusive` up to (but not including) `toExclusive`. /// /// Negative indices are relative to the end of the list. For example, `-1` corresponds to the last element in the list. @@ -1809,54 +1974,35 @@ module { /// Runtime: O(1) /// /// Space: O(1) - public func range(list : List, fromInclusive : Int, toExclusive : Int) : Iter.Iter { - let sz = size(list); - // Convert negative indices to positive and handle bounds - let startInt = if (fromInclusive < 0) { - let s = sz + fromInclusive; - if (s < 0) { 0 } else { s } - } else { - if (fromInclusive > sz) { sz } else { fromInclusive } - }; - let endInt = if (toExclusive < 0) { - let e = sz + toExclusive; - if (e < 0) { 0 } else { e } - } else { - if (toExclusive > sz) { sz } else { toExclusive } + public func range(list : List, fromInclusive : Int, toExclusive : Int) : Iter.Iter = object { + let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + if (start != 0) { + let (block, element) = locate(start - 1); + blockIndex := block; + elementIndex := element + 1 }; - // Convert to Nat (values are non-negative due to bounds checking above) - let start = Prim.abs(startInt); - let end = Prim.abs(endInt); - - object { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - if (start != 0) { - let (block, element) = locate(start - 1); - blockIndex := block; - elementIndex := element + 1 - }; - var db : [var ?T] = list.blocks[blockIndex]; - var dbSize = db.size(); - var index = 0; + var db : [var ?T] = list.blocks[blockIndex]; + var dbSize = db.size(); + var index = 0; - public func next() : ?T { - if (index >= end) return null; - index += 1; + public func next() : ?T { + if (index >= end) return null; + index += 1; - if (elementIndex == dbSize) { - blockIndex += 1; - if (blockIndex >= blocks) return null; - db := list.blocks[blockIndex]; - dbSize := db.size(); - if (dbSize == 0) return null; - elementIndex := 0 - }; - let ret = db[elementIndex]; - elementIndex += 1; - ret - } + if (elementIndex == dbSize) { + blockIndex += 1; + if (blockIndex >= blocks) return null; + db := list.blocks[blockIndex]; + dbSize := db.size(); + if (dbSize == 0) return null; + elementIndex := 0 + }; + let ret = db[elementIndex]; + elementIndex += 1; + ret } }; @@ -1897,6 +2043,55 @@ module { if (toBlock < sz) traverseBlock(blocks[toBlock], f, 0, toElement) }; + /// Returns a new array containing elements from `list` starting at index `fromInclusive` up to (but not including) index `toExclusive`. + /// If the indices are out of bounds, they are clamped to the array bounds. + /// + /// ```motoko include=import + /// let array = List.fromArray([1, 2, 3, 4, 5]); + /// + /// let slice1 = List.sliceToArray(array, 1, 4); + /// assert slice1 == [2, 3, 4]; + /// + /// let slice2 = List.sliceToArray(array, 1, -1); + /// assert slice2 == [2, 3, 4]; + /// ``` + /// + /// Runtime: O(toExclusive - fromInclusive) + /// + /// Space: O(toExclusive - fromInclusive) + public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T] { + let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); + let blocks = list.blocks.size(); + var blockIndex = 0; + var elementIndex = 0; + if (start != 0) { + let (block, element) = locate(start - 1); + blockIndex := block; + elementIndex := element + 1 + }; + var db : [var ?T] = list.blocks[blockIndex]; + var dbSize = db.size(); + + func generator(i : Nat) : T { + if (elementIndex == dbSize) { + blockIndex += 1; + if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); + db := list.blocks[blockIndex]; + dbSize := db.size(); + if (dbSize == 0) Prim.trap(INTERNAL_ERROR); + elementIndex := 0 + }; + switch (db[elementIndex]) { + case (?x) { + elementIndex += 1; + return x + }; + case null Prim.trap(INTERNAL_ERROR) + } + }; + Array.tabulate(end - start, generator) + }; + /// Like `forEachEntryRev` but iterates through the list in reverse order, /// from end to beginning. /// From c77be57ec25f593d50d6a0474408807aa64e3815 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 29 Jun 2025 17:16:46 +0300 Subject: [PATCH 054/123] Added insert function. --- src/List.mo | 51 +++++++++++++++++++++++++++++++++++++++++++++-- test/List.test.mo | 22 ++++++++++++++++++-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/List.mo b/src/List.mo index 5d09d5310..a67ec42b6 100644 --- a/src/List.mo +++ b/src/List.mo @@ -368,11 +368,11 @@ module { let oldElementIndex = result.elementIndex; addRepeatInternal(result, null, size(list)); - + result.blockIndex := oldBlockIndex; result.elementIndex := oldElementIndex; - forEach(list, func item = add(result, item)); + forEach(list, func item = add(result, item)) }; result }; @@ -1061,6 +1061,53 @@ module { } }; + public func insert(list : List, index : Nat, element : T) { + if (index > size(list)) { + Prim.trap "List index out of bounds in insert" + }; + addRepeatInternal(list, null, 1); + + func shift(block : [var ?T], start : Nat, end : Nat, first : ?T) : ?T { + if (start == end) return null; + + var i = end - 1 : Nat; + let last = block[i]; + while (i > start) { + block[i] := block[i - 1]; + i -= 1 + }; + block[start] := first; + last + }; + + let listElement = list.elementIndex; + let listBlock = list.blockIndex; + + let (blockIndex, elementIndex) = locate(index); + let blocks = list.blocks; + + if (listBlock == blockIndex) { + // should be null + ignore shift(blocks[listBlock], elementIndex, listElement, ?element) + } else { + let db = blocks[blockIndex]; + var last = shift(db, elementIndex, db.size(), ?element); + + var i = blockIndex + 1; + + while (i < listBlock) { + let db = blocks[i]; + last := shift(db, 0, db.size(), last); + i += 1 + }; + + if (listBlock < blocks.size()) { + // should be null + ignore shift(blocks[listBlock], 0, listElement, last) + } + } + }; + /// Finds the first index of `element` in `list` using equality of elements defined /// by `equal`. Returns `null` if `element` is not found. /// diff --git a/test/List.test.mo b/test/List.test.mo index a6fcaed99..e3f989f22 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -902,7 +902,7 @@ while (i < locate_n) { // Helper function to run tests func runTest(name : Text, test : (Nat) -> Bool) { - let testSizes = [0, 1, 10, 100]; + let testSizes = [0, 1, 2, 3, 4, 5, 6, 10, 100]; for (n in testSizes.vals()) { if (test(n)) { Debug.print("✅ " # name # " passed for n = " # Nat.toText(n)) @@ -1160,6 +1160,23 @@ func testForEachRange(n : Nat) : Bool { true }; +func testInsert(n : Nat) : Bool { + for (i in Nat.range(0, n)) { + let list = List.tabulate(n, func i = i); + + List.insert(list, i, n); + for (j in Nat.range(0, n + 1)) { + let expectedValue = if (j < i) j else if (j == i) n else j - 1 : Nat; + let value = List.get(list, j); + if (value != expectedValue) { + Debug.print("Insert failed at index " # Nat.toText(j) # ": expected " # debug_show (expectedValue) # ", got " # debug_show (value)); + return false + } + } + }; + true +}; + func testFromIter(n : Nat) : Bool { let iter = Nat.range(1, n + 1); let vec = List.fromIter(iter); @@ -1269,7 +1286,8 @@ func runAllTests() { runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); runTest("testFilterMap", testFilterMap); - runTest("testPure", testPure) + runTest("testPure", testPure); + runTest("testInsert", testInsert) }; // Run all tests From e4717fc449a7d40257d53f62836c36313c9a4523 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 29 Jun 2025 17:40:16 +0300 Subject: [PATCH 055/123] Added remove. --- src/List.mo | 54 +++++++++++++++++++++++++++++++++++++++++++++++ test/List.test.mo | 25 +++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index a67ec42b6..8a11e5663 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1108,6 +1108,60 @@ module { } }; + public func remove(list : List, index : Nat) : T { + if (index >= size(list)) { + Prim.trap "List index out of bounds in remove" + }; + + func shift(block : [var ?T], start : Nat, end : Nat, last : ?T) : ?T { + if (start == end) return null; + + let first = block[start]; + + var i = start; + let to = end - 1 : Nat; + while (i < to) { + block[i] := block[i + 1]; + i += 1 + }; + block[to] := last; + first + }; + + let listElement = list.elementIndex; + let listBlock = list.blockIndex; + + let (blockIndex, elementIndex) = locate(index); + let blocks = list.blocks; + + let ret = if (listBlock == blockIndex) { + shift(blocks[listBlock], elementIndex, listElement, null) + } else { + var first : ?T = null; + if (listBlock < blocks.size()) { + first := shift(blocks[listBlock], 0, listElement, null) + }; + + var i = listBlock - 1 : Nat; + while (i > blockIndex) { + let db = blocks[i]; + first := shift(db, 0, db.size(), first); + i -= 1 + }; + + let db = blocks[blockIndex]; + shift(db, elementIndex, db.size(), first); + }; + + // should be null + ignore removeLast(list); + + switch (ret) { + case (?x) x; + case (null) Prim.trap INTERNAL_ERROR + } + }; + /// Finds the first index of `element` in `list` using equality of elements defined /// by `equal`. Returns `null` if `element` is not found. /// diff --git a/test/List.test.mo b/test/List.test.mo index e3f989f22..a895e3859 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1177,6 +1177,28 @@ func testInsert(n : Nat) : Bool { true }; +func testRemove(n : Nat) : Bool { + for (i in Nat.range(0, n)) { + let list = List.tabulate(n, func i = i); + + let removed = List.remove(list, i); + if (removed != i) { + Debug.print("Remove failed: expected " # Nat.toText(i) # ", got " # debug_show (removed)); + return false + }; + + for (j in Nat.range(0, n - 1)) { + let expectedValue = if (j < i) j else j + 1; + let value = List.get(list, j); + if (value != expectedValue) { + Debug.print("Remove failed at index " # Nat.toText(j) # ": expected " # debug_show (expectedValue) # ", got " # debug_show (value)); + return false + } + } + }; + true +}; + func testFromIter(n : Nat) : Bool { let iter = Nat.range(1, n + 1); let vec = List.fromIter(iter); @@ -1287,7 +1309,8 @@ func runAllTests() { runTest("testFilter", testFilter); runTest("testFilterMap", testFilterMap); runTest("testPure", testPure); - runTest("testInsert", testInsert) + runTest("testInsert", testInsert); + runTest("testRemove", testRemove) }; // Run all tests From db95ae6d29216c923877500d48be408d9b89b9ec Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 29 Jun 2025 18:35:44 +0300 Subject: [PATCH 056/123] Added prevIndexOf, nextIndexOf --- src/List.mo | 80 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/src/List.mo b/src/List.mo index 8a11e5663..e6ebcf948 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1150,7 +1150,7 @@ module { }; let db = blocks[blockIndex]; - shift(db, elementIndex, db.size(), first); + shift(db, elementIndex, db.size(), first) }; // should be null @@ -1182,17 +1182,32 @@ module { /// Runtime: `O(size)` /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. - public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { + public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat = nextIndexOf( + list, + element, + 0, + equal + ); + + public func nextIndexOf(list : List, element : T, fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat { + if (fromInclusive >= size(list)) { + if (not (fromInclusive == 0 and size(list) == 0)) { + Prim.trap "List index out of bounds in nextIndexOf" + } + }; + + let (blockIndex, elementIndex) = locate(fromInclusive); + let blocks = list.blocks; let blockCount = blocks.size(); - var i = 1; + var i = blockIndex; while (i < blockCount) { let db = blocks[i]; let sz = db.size(); if (sz == 0) return null; - var j = 0; + var j = if (i == blockIndex) elementIndex else 0; while (j < sz) { switch (db[j]) { case (?x) if (equal(x, element)) return ?size({ @@ -1224,32 +1239,41 @@ module { /// Runtime: `O(size)` /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. - public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; + public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat = prevIndexOf( + list, + element, + size(list), + equal + ); - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return null; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + public func prevIndexOf(list : List, element : T, fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat { + if (fromExclusive > size(list)) Prim.trap "List index out of bounds in prevIndexOf"; + + let (blockIndex, elementIndex) = locate(fromExclusive); + let blocks = list.blocks; + + var i = blockIndex; + while (i > 0) { + if (i < blocks.size()) { + let db = blocks[i]; + let sz = db.size(); + if (sz > 0) { + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) if (equal(x, element)) return ?size({ + var blockIndex = i; + var elementIndex = j + }); + case null Prim.trap INTERNAL_ERROR + } + } + } }; - switch (db[elementIndex]) { - case (?x) { - if (equal(x, element)) return ?size({ - var blockIndex = blockIndex; - var elementIndex = elementIndex - }) - }; - case (_) Prim.trap(INTERNAL_ERROR) - } - } + i -= 1 + }; + null }; /// Returns the first value in `list` for which `predicate` returns true. From e6952662a7ee0f6f60af2f85c1d765813aef6a35 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 29 Jun 2025 20:41:08 +0300 Subject: [PATCH 057/123] Added sort. --- src/List.mo | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/List.mo b/src/List.mo index e6ebcf948..779b8a247 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1023,7 +1023,7 @@ module { /// List.add(list, 3); /// List.add(list, 1); /// List.add(list, 2); - /// List.sort(list, Nat.compare); + /// List.sortInPlace(list, Nat.compare); /// assert List.toArray(list) == [1, 2, 3]; /// ``` /// @@ -1031,7 +1031,7 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func sort(list : List, compare : (T, T) -> Order.Order) { + public func sortInPlace(list : List, compare : (T, T) -> Order.Order) { if (size(list) < 2) return; let array = toVarArray(list); @@ -1061,6 +1061,26 @@ module { } }; + /// Sorts the elements in the list according to `compare`. + /// Sort is deterministic and stable. + /// + /// ```motoko include=import + /// import Nat "mo:core/Nat"; + /// + /// let list = List.fromArray([4, 2, 6]); + /// let sorted = List.sort(list, Nat.compare); + /// assert List.toArray(sorted) == [2, 4, 6]; + /// ``` + /// Runtime: O(size * log(size)) + /// + /// Space: O(size) + /// *Runtime and space assumes that `compare` runs in O(1) time and space. + public func sort(list : List, compare : (T, T) -> Order.Order) : List { + let array = toVarArray(list); + VarArray.sortInPlace(array, compare); + fromVarArray(array); + }; + public func insert(list : List, index : Nat, element : T) { if (index > size(list)) { Prim.trap "List index out of bounds in insert" From 8557920de8dffa735014b7a2659a8501765cb7d6 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 29 Jun 2025 21:50:14 +0300 Subject: [PATCH 058/123] Fix tests. --- test/List.test.mo | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/List.test.mo b/test/List.test.mo index a895e3859..88a66db7a 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -812,7 +812,7 @@ run( [ test( "sort", - List.sort(list, Nat.compare) |> List.toArray(list), + List.sortInPlace(list, Nat.compare) |> List.toArray(list), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |> M.equals(T.array(T.natTestable, _)) ) ] @@ -1135,9 +1135,10 @@ func testReverse(n : Nat) : Bool { }; func testSort(n : Nat) : Bool { - let vec = List.fromArray(Array.tabulate(n, func(i) = (i * 123) % 100 - 50)); - List.sort(vec, Int.compare); - List.equal(vec, List.fromArray(Array.sort(Array.tabulate(n, func(i) = (i * 123) % 100 - 50), Int.compare)), Int.equal) + let array = Array.tabulate(n, func(i) = (i * 123) % 100 - 50); + let vec = List.fromArray(array); + List.sortInPlace(vec, Int.compare); + List.equal(vec, List.fromArray(Array.sort(array, Int.compare)), Int.equal) }; func testToArray(n : Nat) : Bool { From c43fa9498e43a0d5c04a2d456c4836255751fbe5 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 30 Jun 2025 16:58:35 +0300 Subject: [PATCH 059/123] Try another reverse for each implementation. --- src/List.mo | 91 ++++++++++++----------------------------------------- 1 file changed, 20 insertions(+), 71 deletions(-) diff --git a/src/List.mo b/src/List.mo index 779b8a247..9fcf2020f 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1078,7 +1078,7 @@ module { public func sort(list : List, compare : (T, T) -> Order.Order) : List { let array = toVarArray(list); VarArray.sortInPlace(array, compare); - fromVarArray(array); + fromVarArray(array) }; public func insert(list : List, index : Nat, element : T) { @@ -1963,59 +1963,6 @@ module { } }; - public func forEachEntryChange( - list : List, - f : (i : Nat, oldValue : T) -> (newValue : T) - ) { - var index = 0; - - let blocks = list.blocks; - let blockCount = blocks.size(); - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) db[j] := ?f(index, x); - case null return - }; - index += 1; - j += 1 - }; - i += 1 - } - }; - - public func forEachChange( - list : List, - f : (oldValue : T) -> (newValue : T) - ) { - let blocks = list.blocks; - let blockCount = blocks.size(); - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) db[j] := ?f(x); - case null return - }; - j += 1 - }; - i += 1 - } - }; - /// Applies `f` to each element in `list`. /// /// Example: @@ -2327,25 +2274,27 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func reverseForEach(list : List, f : T -> ()) { - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + var i = blockIndex; + while (i > 0) { + if (i < blocks.size()) { + let db = blocks[i]; + let sz = db.size(); + if (sz > 0) { + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) f(x); + case null Prim.trap INTERNAL_ERROR + } + } + } }; - switch (db[elementIndex]) { - case (?x) f(x); - case (_) Prim.trap(INTERNAL_ERROR) - } + i -= 1 } }; From 54b852017d2ce01e7a591089c6d0f60ff6cec594 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 30 Jun 2025 17:28:20 +0300 Subject: [PATCH 060/123] Optimize and simplify reverse iteration methods. --- src/List.mo | 229 ++++++++++++++++++++++------------------------ test/List.test.mo | 32 ++++++- 2 files changed, 142 insertions(+), 119 deletions(-) diff --git a/src/List.mo b/src/List.mo index a18bf83f2..709fbd2c8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -122,26 +122,26 @@ module { public func toPure(list : List) : PureList.List { var result : PureList.List = null; - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return result; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + while (i > 0) { + let db = blocks[i]; + let sz = db.size(); + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) result := ?(x, result); + case null Prim.trap INTERNAL_ERROR + } }; - switch (db[elementIndex]) { - case (?x) result := ?(x, result); - case (_) Prim.trap(INTERNAL_ERROR) - } - } + i -= 1 + }; + + result }; /// Converts a purely functional `List` to a mutable `List`. @@ -792,31 +792,29 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return null; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + while (i > 0) { + let db = blocks[i]; + let sz = db.size(); + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) if (equal(x, element)) return ?size({ + var blockIndex = i; + var elementIndex = j + }); + case null Prim.trap INTERNAL_ERROR + } }; - switch (db[elementIndex]) { - case (?x) { - if (equal(x, element)) return ?size({ - var blockIndex = blockIndex; - var elementIndex = elementIndex - }) - }; - case (_) Prim.trap(INTERNAL_ERROR) - } - } + i -= 1 + }; + + null }; /// Returns the first value in `list` for which `predicate` returns true. @@ -899,31 +897,29 @@ module { /// /// *Runtime and space assumes that `predicate` runs in `O(1)` time and space. public func findLastIndex(list : List, predicate : T -> Bool) : ?Nat { - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return null; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + while (i > 0) { + let db = blocks[i]; + let sz = db.size(); + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) if (predicate(x)) return ?size({ + var blockIndex = i; + var elementIndex = j + }); + case null Prim.trap INTERNAL_ERROR + } }; - switch (db[elementIndex]) { - case (?x) { - if (predicate(x)) return ?size({ - var blockIndex = blockIndex; - var elementIndex = elementIndex - }) - }; - case (_) Prim.trap(INTERNAL_ERROR) - } - } + i -= 1 + }; + + null }; /// Returns true iff every element in `list` satisfies `predicate`. @@ -1594,27 +1590,26 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func reverseForEachEntry(list : List, f : (Nat, T) -> ()) { - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; - var i = size(list); + var index = 0; - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + while (i > 0) { + let db = blocks[i]; + let sz = db.size(); + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) f(index, x); + case null Prim.trap INTERNAL_ERROR + }; + index += 1 }; - i -= 1; - switch (db[elementIndex]) { - case (?x) f(i, x); - case (_) Prim.trap(INTERNAL_ERROR) - } + i -= 1 } }; @@ -1638,25 +1633,23 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func reverseForEach(list : List, f : T -> ()) { - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + while (i > 0) { + let db = blocks[i]; + let sz = db.size(); + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) f(x); + case null Prim.trap INTERNAL_ERROR + } }; - switch (db[elementIndex]) { - case (?x) f(x); - case (_) Prim.trap(INTERNAL_ERROR) - } + i -= 1 } }; @@ -1999,26 +1992,26 @@ module { public func foldRight(list : List, base : A, combine : (T, A) -> A) : A { var accumulation = base; - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex; - var db : [var ?T] = if (blockIndex < list.blocks.size()) { - list.blocks[blockIndex] - } else { [var] }; + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; - loop { - if (elementIndex != 0) { - elementIndex -= 1 - } else { - blockIndex -= 1; - if (blockIndex == 0) return accumulation; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 + var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + while (i > 0) { + let db = blocks[i]; + let sz = db.size(); + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) accumulation := combine(x, accumulation); + case null Prim.trap INTERNAL_ERROR + } }; - switch (db[elementIndex]) { - case (?x) accumulation := combine(x, accumulation); - case (_) Prim.trap(INTERNAL_ERROR) - } - } + i -= 1 + }; + + accumulation }; /// Reverses the order of elements in `list` by overwriting in place. diff --git a/test/List.test.mo b/test/List.test.mo index 090e8d8e8..853264abd 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1229,6 +1229,34 @@ func testPure(n : Nat) : Bool { true }; +func testReverseForEach(n : Nat) : Bool { + let vec = List.fromArray(Array.tabulate(n, func(i) = i + 1)); + var revSum = 0; + List.reverseForEach(vec, func(x) = revSum += x); + let expectedReversed = n * (n + 1) / 2; + + if (revSum != expectedReversed) { + Debug.print("Reverse forEach failed: expected " # Nat.toText(expectedReversed) # ", got " # Nat.toText(revSum)); + return false + }; + + true +}; + +func testForEach(n : Nat) : Bool { + let vec = List.fromArray(Array.tabulate(n, func(i) = i + 1)); + var revSum = 0; + List.forEach(vec, func(x) = revSum += x); + let expectedReversed = n * (n + 1) / 2; + + if (revSum != expectedReversed) { + Debug.print("ForEach failed: expected " # Nat.toText(expectedReversed) # ", got " # Nat.toText(revSum)); + return false + }; + + true +}; + // Run all tests func runAllTests() { runTest("testNew", testNew); @@ -1253,7 +1281,9 @@ func runAllTests() { runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); runTest("testFilterMap", testFilterMap); - runTest("testPure", testPure) + runTest("testPure", testPure); + runTest("testReverseForEach", testReverseForEach); + runTest("testForEach", testForEach); }; // Run all tests From 6c36a6fff58a93abb535d7db565370d40e121f31 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 30 Jun 2025 17:41:12 +0300 Subject: [PATCH 061/123] Simplify prevIndexOf. --- src/List.mo | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/List.mo b/src/List.mo index d5352889f..452078b50 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1269,30 +1269,27 @@ module { public func prevIndexOf(list : List, element : T, fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat { if (fromExclusive > size(list)) Prim.trap "List index out of bounds in prevIndexOf"; - let (blockIndex, elementIndex) = locate(fromExclusive); let blocks = list.blocks; + let (blockIndex, elementIndex) = locate(fromExclusive); - var i = blockIndex; + var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; while (i > 0) { - if (i < blocks.size()) { - let db = blocks[i]; - let sz = db.size(); - if (sz > 0) { - var j = if (i == blockIndex) elementIndex else sz; - while (j > 0) { - j -= 1; - switch (db[j]) { - case (?x) if (equal(x, element)) return ?size({ - var blockIndex = i; - var elementIndex = j - }); - case null Prim.trap INTERNAL_ERROR - } - } + let db = blocks[i]; + let sz = db.size(); + var j = if (i == blockIndex) elementIndex else sz; + while (j > 0) { + j -= 1; + switch (db[j]) { + case (?x) if (equal(x, element)) return ?size({ + var blockIndex = i; + var elementIndex = j + }); + case null Prim.trap INTERNAL_ERROR } }; i -= 1 }; + null }; From c04aeedc2d71fdd4c214951d1cdacccba2d9315b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 30 Jun 2025 19:09:07 +0300 Subject: [PATCH 062/123] Added tests for new methods. --- src/List.mo | 2 +- test/List.test.mo | 242 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 227 insertions(+), 17 deletions(-) diff --git a/src/List.mo b/src/List.mo index 452078b50..c724b633c 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2097,7 +2097,7 @@ module { }; var db : [var ?T] = list.blocks[blockIndex]; var dbSize = db.size(); - var index = 0; + var index = fromInclusive; public func next() : ?T { if (index >= end) return null; diff --git a/test/List.test.mo b/test/List.test.mo index d2eca9278..50958c017 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -15,6 +15,7 @@ import Int "../src/Int"; import Debug "../src/Debug"; import { Tuple2 } "../src/Tuples"; import PureList "../src/pure/List"; +import VarArray "../src/VarArray"; let { run; test; suite } = Suite; @@ -819,6 +820,75 @@ run( ) ); +func joinWith(xs : List.List, sep : Text) : Text { + let size = List.size(xs); + + if (size == 0) return ""; + if (size == 1) return List.get(xs, 0); + + var result = List.get(xs, 0); + var i = 0; + label l loop { + i += 1; + if (i >= size) { break l }; + result #= sep # List.get(xs, i) + }; + result +}; + +func listTestable(testableA : T.Testable) : T.Testable> { + { + display = func(xs : List.List) : Text = "[var " # joinWith(List.map(xs, testableA.display), ", ") # "]"; + equals = func(xs1 : List.List, xs2 : List.List) : Bool = List.equal(xs1, xs2, testableA.equals) + } +}; + +run( + suite( + "mapResult", + [ + test( + "mapResult", + List.mapResult( + List.fromArray([1, 2, 3]), + func x { + if (x >= 0) { #ok(Int.abs x) } else { #err "error message" } + } + ), + M.equals(T.result, Text>(listTestable(T.natTestable), T.textTestable, #ok(List.fromArray([1, 2, 3])))) + ), + Suite.test( + "mapResult fail first", + List.mapResult( + List.fromArray([-1, 2, 3]), + func x { + if (x >= 0) { #ok(Int.abs x) } else { #err "error message" } + } + ), + M.equals(T.result, Text>(listTestable(T.natTestable), T.textTestable, #err "error message")) + ), + Suite.test( + "mapResult fail last", + List.mapResult( + List.fromArray([1, 2, -3]), + func x { + if (x >= 0) { #ok(Int.abs x) } else { #err "error message" } + } + ), + M.equals(T.result, Text>(listTestable(T.natTestable), T.textTestable, #err "error message")) + ), + Suite.test( + "mapResult empty", + List.mapResult( + List.fromArray([]), + func x = #ok x + ), + M.equals(T.result, Text>(listTestable(T.natTestable), T.textTestable, #ok(List.fromArray([])))) + ) + ] + ) +); + /* --------------------------------------- */ func locate_readable(index : Nat) : (Nat, Nat) { @@ -1069,6 +1139,82 @@ func testMap(n : Nat) : Bool { List.equal(mapped, List.fromArray(Array.tabulate(n, func(i) = i * 2)), Nat.equal) }; +func testMapEntries(n : Nat) : Bool { + let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + let mapped = List.mapEntries(vec, func(i, x) = i * x); + List.equal(mapped, List.fromArray(Array.tabulate(n, func(i) = i * i)), Nat.equal) +}; + +func testMapInPlace(n : Nat) : Bool { + let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + List.mapInPlace(vec, func(x) = x * 2); + List.equal(vec, List.fromArray(Array.tabulate(n, func(i) = i * 2)), Nat.equal) +}; + +func testFlatMap(n : Nat) : Bool { + let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + let flatMapped = List.flatMap(vec, func(x) = [x, x].vals()); + + let expected = List.fromArray(Array.tabulate(2 * n, func(i) = i / 2)); + List.equal(flatMapped, expected, Nat.equal) +}; + +func testRange(n : Nat) : Bool { + if (n > 10) return true; // Skip large ranges for performance + let vec = List.tabulate(n, func(i) = i); + for (left in Nat.range(0, n)) { + for (right in Nat.range(left, n + 1)) { + let range = Iter.toArray(List.range(vec, left, right)); + let expected = Array.tabulate(right - left, func(i) = left + i); + if (range != expected) { + Debug.print( + "Range mismatch for left = " # Nat.toText(left) # ", right = " # Nat.toText(right) # ": expected " # debug_show (expected) # ", got " # debug_show (range) + ); + return false + } + } + }; + true +}; + +func testSliceToArray(n : Nat) : Bool { + if (n > 10) return true; // Skip large ranges for performance + let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + for (left in Nat.range(0, n)) { + for (right in Nat.range(left, n + 1)) { + let slice = List.sliceToArray(vec, left, right); + let expected = Array.tabulate(right - left, func(i) = left + i); + if (slice != expected) { + Debug.print( + "Slice mismatch for left = " # Nat.toText(left) # ", right = " # Nat.toText(right) # ": expected " # debug_show (expected) # ", got " # debug_show (slice) + ); + return false + } + } + }; + true +}; + +func testForEachRange(n : Nat) : Bool { + if (n > 10) return true; // Skip large ranges for performance + let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + + for (left in Nat.range(0, n)) { + for (right in Nat.range(left, n + 1)) { + let expected = VarArray.tabulate(right - left, func(i) = left + i); + let result = VarArray.repeat(0, right - left); + List.forEachRange(vec, func(i) = result[i - left] := i, left, right); + if (Array.fromVarArray(result) != Array.fromVarArray(expected)) { + Debug.print( + "ForEachRange mismatch for left = " # Nat.toText(left) # ", right = " # Nat.toText(right) # ": expected " # debug_show (expected) # ", got " # debug_show (result) + ); + return false + } + } + }; + true +}; + func testIndexOf(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(2 * n, func(i) = i % n)); if (n == 0) { @@ -1128,6 +1274,7 @@ func testContains(n : Nat) : Bool { true }; + func testReverse(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); List.reverseInPlace(vec); @@ -1146,21 +1293,6 @@ func testToArray(n : Nat) : Bool { Array.equal(List.toArray(vec), Array.tabulate(n, func(i) = i), Nat.equal) }; -func testForEachRange(n : Nat) : Bool { - let vec = List.fromArray(Array.tabulate(n, func(i) = i)); - - var sum = 0; - List.forEachRange(vec, func(x) = sum += x, 0, n); - - var checkSum = 0; - List.forEach(vec, func(x) = checkSum += x); - if (sum != checkSum) { - Debug.print("ForEachRange failed: expected " # Nat.toText(checkSum) # ", got " # Nat.toText(sum)); - return false - }; - true -}; - func testInsert(n : Nat) : Bool { for (i in Nat.range(0, n)) { let list = List.tabulate(n, func i = i); @@ -1312,6 +1444,76 @@ func testForEach(n : Nat) : Bool { true }; +func testFlatten(n : Nat) : Bool { + let vec = List.fromArray>( + Array.tabulate>( + n, + func(i) = List.fromArray(Array.tabulate(i + 1, func(j) = j)) + ) + ); + let flattened = List.flatten(vec); + let expectedSize = (n * (n + 1)) / 2; + + if (List.size(flattened) != expectedSize) { + Debug.print("Flatten size mismatch: expected " # Nat.toText(expectedSize) # ", got " # Nat.toText(List.size(flattened))); + return false + }; + + for (i in Nat.range(0, n)) { + for (j in Nat.range(0, i + 1)) { + if (List.get(flattened, (i * (i + 1)) / 2 + j) != j) { + Debug.print("Flatten value mismatch at index " # Nat.toText((i * (i + 1)) / 2 + j) # ": expected " # Nat.toText(j)); + return false + } + } + }; + + true +}; + +func testJoin(n : Nat) : Bool { + let iter = Array.tabulate>( + n, + func(i) = List.fromArray(Array.tabulate(i + 1, func(j) = j)) + ).vals(); + let flattened = List.join(iter); + let expectedSize = (n * (n + 1)) / 2; + + if (List.size(flattened) != expectedSize) { + Debug.print("Flatten size mismatch: expected " # Nat.toText(expectedSize) # ", got " # Nat.toText(List.size(flattened))); + return false + }; + + for (i in Nat.range(0, n)) { + for (j in Nat.range(0, i + 1)) { + if (List.get(flattened, (i * (i + 1)) / 2 + j) != j) { + Debug.print("Flatten value mismatch at index " # Nat.toText((i * (i + 1)) / 2 + j) # ": expected " # Nat.toText(j)); + return false + } + } + }; + + true +}; + +func testTabulate(n : Nat) : Bool { + let tabu = List.tabulate(n, func(i) = i); + + if (List.size(tabu) != n) { + Debug.print("Tabulate size mismatch: expected " # Nat.toText(n) # ", got " # Nat.toText(List.size(tabu))); + return false + }; + + for (i in Nat.range(0, n)) { + if (List.get(tabu, i) != i) { + Debug.print("Tabulate value mismatch at index " # Nat.toText(i) # ": expected " # Nat.toText(i) # ", got " # Nat.toText(List.get(tabu, i))); + return false + } + }; + + true +}; + // Run all tests func runAllTests() { runTest("testNew", testNew); @@ -1325,6 +1527,11 @@ func runAllTests() { runTest("testClear", testClear); runTest("testClone", testClone); runTest("testMap", testMap); + runTest("testMapEntries", testMapEntries); + runTest("testMapInPlace", testMapInPlace); + runTest("testFlatMap", testFlatMap); + runTest("testRange", testRange); + runTest("testSliceToArray", testSliceToArray); runTest("testIndexOf", testIndexOf); runTest("testLastIndexOf", testLastIndexOf); runTest("testContains", testContains); @@ -1341,7 +1548,10 @@ func runAllTests() { runTest("testReverseForEach", testReverseForEach); runTest("testForEach", testForEach); runTest("testInsert", testInsert); - runTest("testRemove", testRemove) + runTest("testRemove", testRemove); + runTest("testFlatten", testFlatten); + runTest("testJoin", testJoin); + runTest("testTabulate", testTabulate) }; // Run all tests From 3ffb0909ff96d7fe7fff0a7ba936d757ad3733bf Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 30 Jun 2025 19:15:48 +0300 Subject: [PATCH 063/123] Update api lock json. --- validation/api/api.lock.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index ae73d9a32..935eca0d1 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -524,10 +524,13 @@ "public func findIndex(list : List, predicate : T -> Bool) : ?Nat", "public func findLastIndex(list : List, predicate : T -> Bool) : ?Nat", "public func first(list : List) : ?T", + "public func flatMap(list : List, k : T -> Iter.Iter) : List", + "public func flatten(lists : List>) : List", "public func foldLeft(list : List, base : A, combine : (A, T) -> A) : A", "public func foldRight(list : List, base : A, combine : (T, A) -> A) : A", "public func forEach(list : List, f : T -> ())", "public func forEachEntry(list : List, f : (Nat, T) -> ())", + "public func forEachRange(list : List, f : T -> (), fromInclusive : Nat, toExclusive : Nat)", "public func fromArray(array : [T]) : List", "public func fromIter(iter : Iter.Iter) : List", "public func fromPure(pure : PureList.List) : List", @@ -535,15 +538,24 @@ "public func get(list : List, index : Nat) : T", "public func getOpt(list : List, index : Nat) : ?T", "public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat", + "public func insert(list : List, index : Nat, element : T)", "public func isEmpty(list : List) : Bool", + "public func join(lists : Iter.Iter>) : List", "public func keys(list : List) : Iter.Iter", "public func last(list : List) : ?T", "public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat", "public type List", "public func map(list : List, f : T -> R) : List", + "public func mapEntries(list : List, f : (T, Nat) -> R) : List", + "public func mapInPlace(list : List, f : T -> T)", + "public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E>", "public func max(list : List, compare : (T, T) -> Order.Order) : ?T", "public func min(list : List, compare : (T, T) -> Order.Order) : ?T", + "public func nextIndexOf(list : List, element : T, fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat", + "public func prevIndexOf(list : List, element : T, fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat", "public func put(list : List, index : Nat, value : T)", + "public func range(list : List, fromInclusive : Int, toExclusive : Int) : Iter.Iter", + "public func remove(list : List, index : Nat) : T", "public func removeLast(list : List) : ?T", "public func repeat(initValue : T, size : Nat) : List", "public func reverse(list : List) : List", @@ -553,8 +565,11 @@ "public func reverseInPlace(list : List)", "public func reverseValues(list : List) : Iter.Iter", "public func singleton(element : T) : List", - "public func size(list : List) : Nat", - "public func sort(list : List, compare : (T, T) -> Order.Order)", + "public func size( list : { var blockIndex : Nat; var elementIndex : Nat } ) : Nat", + "public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", + "public func sort(list : List, compare : (T, T) -> Order.Order) : List", + "public func sortInPlace(list : List, compare : (T, T) -> Order.Order)", + "public func tabulate(size : Nat, generator : Nat -> T) : List", "public func toArray(list : List) : [T]", "public func toPure(list : List) : PureList.List", "public func toText(list : List, f : T -> Text) : Text", From e72a5cbdddcaef053af64751a68d2cb7563350bc Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 30 Jun 2025 19:17:35 +0300 Subject: [PATCH 064/123] Fix docs. --- src/List.mo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/List.mo b/src/List.mo index c724b633c..f4c5e174a 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2790,8 +2790,8 @@ module { /// /// Example: /// ```motoko include=import - /// import Nat "mo:base/Nat"; - /// import Iter "mo:base/Iter"; + /// import Nat "mo:core/Nat"; + /// import Iter "mo:core/Iter"; /// /// let list1 = List.fromArray([1,2,3]); /// let list2 = List.fromArray([4,5,6]); @@ -2835,8 +2835,8 @@ module { /// /// Example: /// ```motoko include=import - /// import Nat "mo:base/Nat"; - /// import Iter "mo:base/Iter"; + /// import Nat "mo:core/Nat"; + /// import Iter "mo:core/Iter"; /// /// let list1 = List.fromArray([1, 2, 3]); /// let list2 = List.fromArray([4, 5, 6]); From f1fdc9183e665d5588ee6a4b2d1c41f16a8f872b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 1 Jul 2025 19:34:19 +0300 Subject: [PATCH 065/123] Added tests. --- test/List.test.mo | 104 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/test/List.test.mo b/test/List.test.mo index 50958c017..ea71e417d 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1289,15 +1289,39 @@ func testSort(n : Nat) : Bool { }; func testToArray(n : Nat) : Bool { - let vec = List.fromArray(Array.tabulate(n, func(i) = i)); - Array.equal(List.toArray(vec), Array.tabulate(n, func(i) = i), Nat.equal) + let array = Array.tabulate(n, func(i) = i); + let vec = List.fromArray(array); + Array.equal(List.toArray(vec), array, Nat.equal) +}; + +func testToVarArray(n : Nat) : Bool { + let array = VarArray.tabulate(n, func(i) = i); + let vec = List.tabulate(n, func(i) = i); + VarArray.equal(List.toVarArray(vec), array, Nat.equal) +}; + +func testFromVarArray(n : Nat) : Bool { + let array = VarArray.tabulate(n, func(i) = i); + let vec = List.fromVarArray(array); + List.equal(vec, List.fromArray(Array.fromVarArray(array)), Nat.equal) +}; + +func testFromArray(n : Nat) : Bool { + let array = Array.tabulate(n, func(i) = i); + let vec = List.fromArray(array); + List.equal(vec, List.fromArray(array), Nat.equal) }; func testInsert(n : Nat) : Bool { - for (i in Nat.range(0, n)) { + for (i in Nat.range(0, n + 1)) { let list = List.tabulate(n, func i = i); - List.insert(list, i, n); + + if (List.size(list) != n + 1) { + Debug.print("Insert failed: expected size " # Nat.toText(n + 1) # ", got " # Nat.toText(List.size(list))); + return false + }; + for (j in Nat.range(0, n + 1)) { let expectedValue = if (j < i) j else if (j == i) n else j - 1 : Nat; let value = List.get(list, j); @@ -1313,13 +1337,18 @@ func testInsert(n : Nat) : Bool { func testRemove(n : Nat) : Bool { for (i in Nat.range(0, n)) { let list = List.tabulate(n, func i = i); - let removed = List.remove(list, i); + if (removed != i) { Debug.print("Remove failed: expected " # Nat.toText(i) # ", got " # debug_show (removed)); return false }; + if (List.size(list) != (n - 1 : Nat)) { + Debug.print("Remove failed: expected size " # Nat.toText(n - 1) # ", got " # Nat.toText(List.size(list))); + return false + }; + for (j in Nat.range(0, n - 1)) { let expectedValue = if (j < i) j else j + 1; let value = List.get(list, j); @@ -1514,6 +1543,64 @@ func testTabulate(n : Nat) : Bool { true }; +func testNextIndexOf(n : Nat) : Bool { + func nextIndexOf(vec : List.List, element : Nat, from : Nat) : ?Nat { + for (i in Nat.range(from, List.size(vec))) { + if (List.get(vec, i) == element) { + return ?i + } + }; + return null + }; + + if (n > 10) return true; // Skip large vectors for performance + + let vec = List.tabulate(n, func(i) = i); + for (from in Nat.range(0, n)) { + for (element in Nat.range(0, n + 1)) { + let actual = List.nextIndexOf(vec, element, from, Nat.equal); + let expected = nextIndexOf(vec, element, from); + if (expected != actual) { + Debug.print( + "nextIndexOf failed for element " # Nat.toText(element) # " from index " # Nat.toText(from) # ": expected " # debug_show (expected) # ", got " # debug_show (actual) + ); + return false + } + } + }; + true +}; + +func testPrevIndexOf(n : Nat) : Bool { + func prevIndexOf(vec : List.List, element : Nat, from : Nat) : ?Nat { + var i = from; + while (i > 0) { + i -= 1; + if (List.get(vec, i) == element) { + return ?i + } + }; + return null + }; + + if (n > 10) return true; // Skip large vectors for performance + + let vec = List.tabulate(n, func(i) = i); + for (from in Nat.range(1, n + 1)) { + for (element in Nat.range(0, n + 1)) { + let actual = List.prevIndexOf(vec, element, from, Nat.equal); + let expected = prevIndexOf(vec, element, from); + if (expected != actual) { + Debug.print( + "prevIndexOf failed for element " # Nat.toText(element) # " from index " # Nat.toText(from) # ": expected " # debug_show (expected) # ", got " # debug_show (actual) + ); + return false + } + } + }; + true +}; + // Run all tests func runAllTests() { runTest("testNew", testNew); @@ -1538,6 +1625,9 @@ func runAllTests() { runTest("testReverse", testReverse); runTest("testSort", testSort); runTest("testToArray", testToArray); + runTest("testToVarArray", testToVarArray); + runTest("testFromVarArray", testFromVarArray); + runTest("testFromArray", testFromArray); runTest("testFromIter", testFromIter); runTest("testForEachRange", testForEachRange); runTest("testFoldLeft", testFoldLeft); @@ -1551,7 +1641,9 @@ func runAllTests() { runTest("testRemove", testRemove); runTest("testFlatten", testFlatten); runTest("testJoin", testJoin); - runTest("testTabulate", testTabulate) + runTest("testTabulate", testTabulate); + runTest("testNextIndexOf", testNextIndexOf); + runTest("testPrevIndexOf", testPrevIndexOf) }; // Run all tests From 708324af52a5f78cfa89ab5680c65b3f996a190f Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 1 Jul 2025 19:55:38 +0300 Subject: [PATCH 066/123] Strengthen tests. --- test/List.test.mo | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/test/List.test.mo b/test/List.test.mo index ea71e417d..a62852eed 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -972,7 +972,7 @@ while (i < locate_n) { // Helper function to run tests func runTest(name : Text, test : (Nat) -> Bool) { - let testSizes = [0, 1, 2, 3, 4, 5, 6, 10, 100]; + let testSizes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100]; for (n in testSizes.vals()) { if (test(n)) { Debug.print("✅ " # name # " passed for n = " # Nat.toText(n)) @@ -990,7 +990,17 @@ func testNew(n : Nat) : Bool { func testInit(n : Nat) : Bool { let vec = List.repeat(1, n); - List.size(vec) == n and (n == 0 or (List.get(vec, 0) == 1 and List.get(vec, n - 1 : Nat) == 1)) + if (List.size(vec) != n) { + Debug.print("Init failed: expected size " # Nat.toText(n) # ", got " # Nat.toText(List.size(vec))); + return false + }; + for (i in Nat.range(0, n)) { + if (List.get(vec, i) != 1) { + Debug.print("Init failed at index " # Nat.toText(i) # ": expected 1, got " # Nat.toText(List.get(vec, i))); + return false + } + }; + true }; func testAdd(n : Nat) : Bool { @@ -1112,13 +1122,16 @@ func testGetOpt(n : Nat) : Bool { }; func testPut(n : Nat) : Bool { - let vec = List.fromArray(Array.tabulate(n, func(i) = i)); - if (n == 0) { - true - } else { - List.put(vec, n - 1 : Nat, 100); - List.get(vec, n - 1 : Nat) == 100 - } + let vec = List.fromArray(Array.repeat(0, n)); + for (i in Nat.range(0, n)) { + List.put(vec, i, i + 1); + let value = List.get(vec, i); + if (value != i + 1) { + Debug.print("put: Mismatch at index " # Nat.toText(i) # ": expected " # Nat.toText(i + 1) # ", got " # Nat.toText(value)); + return false + } + }; + true }; func testClear(n : Nat) : Bool { From 7beb288f8434924241cd54b5c77376a96117f551 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 2 Jul 2025 17:44:23 +0300 Subject: [PATCH 067/123] Strengthen tests. --- src/List.mo | 20 +++++++----------- test/List.test.mo | 52 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/List.mo b/src/List.mo index f4c5e174a..1b8329d9b 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1202,19 +1202,13 @@ module { /// Runtime: `O(size)` /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. - public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat = nextIndexOf( - list, - element, - 0, - equal - ); + public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { + if (isEmpty(list)) return null; + nextIndexOf(list, element, 0, equal) + }; public func nextIndexOf(list : List, element : T, fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat { - if (fromInclusive >= size(list)) { - if (not (fromInclusive == 0 and size(list) == 0)) { - Prim.trap "List index out of bounds in nextIndexOf" - } - }; + if (fromInclusive >= size(list)) Prim.trap "List index out of bounds in nextIndexOf"; let (blockIndex, elementIndex) = locate(fromInclusive); @@ -2341,7 +2335,7 @@ module { let blocks = list.blocks; let blockCount = blocks.size(); - var i = 1; + var i = 2; while (i < blockCount) { let db = blocks[i]; let sz = db.size(); @@ -2393,7 +2387,7 @@ module { let blocks = list.blocks; let blockCount = blocks.size(); - var i = 1; + var i = 2; while (i < blockCount) { let db = blocks[i]; let sz = db.size(); diff --git a/test/List.test.mo b/test/List.test.mo index a62852eed..b2651868b 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1599,7 +1599,7 @@ func testPrevIndexOf(n : Nat) : Bool { if (n > 10) return true; // Skip large vectors for performance let vec = List.tabulate(n, func(i) = i); - for (from in Nat.range(1, n + 1)) { + for (from in Nat.range(0, n + 1)) { for (element in Nat.range(0, n + 1)) { let actual = List.prevIndexOf(vec, element, from, Nat.equal); let expected = prevIndexOf(vec, element, from); @@ -1614,6 +1614,52 @@ func testPrevIndexOf(n : Nat) : Bool { true }; +func testMin(n : Nat) : Bool { + if (n == 0) { + let vec = List.empty(); + if (List.min(vec, Nat.compare) != null) { + Debug.print("Min on empty list should return null"); + return false + }; + return true + }; + + let vec = List.fromArray(Array.tabulate(n, func(i) = i + 1)); + for (i in Nat.range(0, n)) { + List.put(vec, i, 0); + let min = List.min(vec, Nat.compare); + if (min != ?0) { + Debug.print("Min failed: expected ?0, got " # debug_show (min)); + return false + }; + List.put(vec, i, i + 1); + }; + true +}; + +func testMax(n : Nat) : Bool { + if (n == 0) { + let vec = List.empty(); + if (List.max(vec, Nat.compare) != null) { + Debug.print("Max on empty list should return null"); + return false + }; + return true + }; + + let vec = List.fromArray(Array.tabulate(n, func(i) = i + 1)); + for (i in Nat.range(0, n)) { + List.put(vec, i, n + 1); + let max = List.max(vec, Nat.compare); + if (max != ?(n + 1)) { + Debug.print("Max failed: expected ?" # Nat.toText(n + 1) # ", got " # debug_show (max)); + return false + }; + List.put(vec, i, i + 1); + }; + true +}; + // Run all tests func runAllTests() { runTest("testNew", testNew); @@ -1656,7 +1702,9 @@ func runAllTests() { runTest("testJoin", testJoin); runTest("testTabulate", testTabulate); runTest("testNextIndexOf", testNextIndexOf); - runTest("testPrevIndexOf", testPrevIndexOf) + runTest("testPrevIndexOf", testPrevIndexOf); + runTest("testMin", testMin); + runTest("testMax", testMax) }; // Run all tests From 5c565eb46dc9467d9b6a109230dbb5ea015f4721 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 3 Jul 2025 16:50:50 +0300 Subject: [PATCH 068/123] Added docs. --- src/List.mo | 82 ++++++++++++++++++++++++++++++++++++++++------- test/List.test.mo | 21 ++++++------ 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/src/List.mo b/src/List.mo index 1b8329d9b..e32c0c0a2 100644 --- a/src/List.mo +++ b/src/List.mo @@ -146,14 +146,14 @@ module { result }; - /// Converts a purely functional `List` to a mutable `List`. + /// Converts a purely functional `List` to a `List`. /// /// Example: /// ```motoko include=import /// import PureList "mo:core/pure/List"; /// /// let pureList = PureList.fromArray([1, 2, 3]); - /// let list = List.fromPure(pureList); // converts to mutable List + /// let list = List.fromPure(pureList); // converts to List /// ``` /// /// Runtime: `O(size)` @@ -313,11 +313,11 @@ module { /// ```motoko include=import /// import Nat "mo:core/Nat"; /// - /// let lists = List.fromArray([ - /// List.fromArray([0, 1, 2]), List.fromArray([2, 3]), List.fromArray([]), List.fromArray([4]) + /// let lists = List.fromArray>([ + /// List.fromArray([0, 1, 2]), List.fromArray([2, 3]), List.fromArray([]), List.fromArray([4]) /// ]); /// let flatList = List.flatten(lists); - /// assert List.equal(flatList, List.fromArray([0, 1, 2, 2, 3, 4]), Nat.equal); + /// assert List.equal(flatList, List.fromArray([0, 1, 2, 2, 3, 4]), Nat.equal); /// ``` /// /// Runtime: O(number of elements in list) @@ -353,9 +353,9 @@ module { /// ```motoko include=import /// import Nat "mo:core/Nat"; /// - /// let lists = [List.fromArray([0, 1, 2]), List.fromArray([2, 3]), List.fromArray([]), List.fromArray([4])]; + /// let lists = [List.fromArray([0, 1, 2]), List.fromArray([2, 3]), List.fromArray([]), List.fromArray([4])]; /// let joinedList = List.join(lists.vals()); - /// assert List.equal(joinedList, List.fromArray([0, 1, 2, 2, 3, 4]), Nat.equal); + /// assert List.equal(joinedList, List.fromArray([0, 1, 2, 2, 3, 4]), Nat.equal); /// ``` /// /// Runtime: O(number of elements in list) @@ -542,7 +542,7 @@ module { /// ```motoko include=import /// import Result "mo:core/Result"; /// - /// let list = List.fromArray([4, 3, 2, 1, 0]); + /// let list = List.fromArray([4, 3, 2, 1, 0]); /// // divide 100 by every element in the list /// let result = List.mapResult(list, func x { /// if (x > 0) { @@ -699,7 +699,7 @@ module { /// ```motoko include=import /// import Int "mo:core/Int" /// - /// let list = List.fromArray([1, 2, 3, 4]); + /// let list = List.fromArray([1, 2, 3, 4]); /// let newList = List.flatMap(list, func x = [x, -x].vals()); /// assert List.equal(newList, List.fromArray([1, -1, 2, -2, 3, -3, 4, -4]), Int.equal); /// ``` @@ -1081,6 +1081,19 @@ module { fromVarArray(array) }; + /// Inserts `element` at `index` in the list, shifting existing elements to the right. + /// Traps if `index > size`. Indexing is zero-based. + /// + /// Example: + /// ```motoko include=import + /// let list = List.fromArray([1, 2, 4]); + /// List.insert(list, 2, 3); // inserts 3 at index 2 + /// assert List.toArray(list) == [1, 2, 3, 4]; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) public func insert(list : List, index : Nat, element : T) { if (index > size(list)) { Prim.trap "List index out of bounds in insert" @@ -1128,6 +1141,20 @@ module { } }; + /// Removes the element at `index` from the list, shifting existing elements to the left. + /// Traps if `index >= size`. Indexing is zero-based. + /// + /// Example: + /// ```motoko include=import + /// let list = List.fromArray([1, 2, 3, 4]); + /// let removed = List.remove(list, 2); // removes element at index 2 + /// assert removed == 3; + /// assert List.toArray(list) == [1, 2, 4]; + /// ``` + /// + /// Runtime: `O(size)` + /// + /// Space: `O(1)` public func remove(list : List, index : Nat) : T { if (index >= size(list)) { Prim.trap "List index out of bounds in remove" @@ -1207,6 +1234,23 @@ module { nextIndexOf(list, element, 0, equal) }; + /// Returns the index of the next occurence of `element` in the `list` starting from the `from` index (inclusive). + /// + /// ```motoko include=import + /// import Char "mo:core/Char"; + /// let list = List.fromArray(['c', 'o', 'f', 'f', 'e', 'e']); + /// assert List.nextIndexOf(list, 'c', 0, Char.equal) == ?0; + /// assert List.nextIndexOf(list, 'f', 0, Char.equal) == ?2; + /// assert List.nextIndexOf(list, 'f', 2, Char.equal) == ?2; + /// assert List.nextIndexOf(list, 'f', 3, Char.equal) == ?3; + /// assert List.nextIndexOf(list, 'f', 4, Char.equal) == null; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. public func nextIndexOf(list : List, element : T, fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat { if (fromInclusive >= size(list)) Prim.trap "List index out of bounds in nextIndexOf"; @@ -1260,6 +1304,20 @@ module { equal ); + /// Returns the index of the previous occurence of `element` in the `list` starting from the `from` index (exclusive). + /// + /// ```motoko include=import + /// import Char "mo:core/Char"; + /// let list = List.fromArray(['c', 'o', 'f', 'f', 'e', 'e']); + /// assert List.prevIndexOf(list, 'c', list.size(), Char.equal) == ?0; + /// assert List.prevIndexOf(list, 'e', list.size(), Char.equal) == ?5; + /// assert List.prevIndexOf(list, 'e', 5, Char.equal) == ?4; + /// assert List.prevIndexOf(list, 'e', 4, Char.equal) == null; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) public func prevIndexOf(list : List, element : T, fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat { if (fromExclusive > size(list)) Prim.trap "List index out of bounds in prevIndexOf"; @@ -1930,7 +1988,7 @@ module { if (list.blockIndex == 1 and list.elementIndex == 0) null else list.blocks[1][0] }; - /// Returns the last element of `list`. Traps if `list` is empty. + /// Returns the last element of `list`. Returns null if `list` is empty. /// /// Example: /// ```motoko include=import @@ -2062,7 +2120,7 @@ module { /// If the first index is greater than the second, the function returns an empty iterator. /// /// ```motoko include=import - /// let list = List.fromArray([1, 2, 3, 4, 5]); + /// let list = List.fromArray([1, 2, 3, 4, 5]); /// let iter1 = List.range(list, 3, list.size()); /// assert iter1.next() == ?4; /// assert iter1.next() == ?5; @@ -2152,7 +2210,7 @@ module { /// If the indices are out of bounds, they are clamped to the array bounds. /// /// ```motoko include=import - /// let array = List.fromArray([1, 2, 3, 4, 5]); + /// let array = List.fromArray([1, 2, 3, 4, 5]); /// /// let slice1 = List.sliceToArray(array, 1, 4); /// assert slice1 == [2, 3, 4]; diff --git a/test/List.test.mo b/test/List.test.mo index b2651868b..1cb7e279a 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1090,10 +1090,10 @@ func testGet(n : Nat) : Bool { }; func testGetOpt(n : Nat) : Bool { - let vec = List.fromArray(Array.tabulate(n, func(i) = i + 1)); + let vec = List.tabulate(n, func(i) = i); - for (i in Nat.range(1, n + 1)) { - switch (List.getOpt(vec, i - 1 : Nat)) { + for (i in Nat.range(0, n)) { + switch (List.getOpt(vec, i)) { case (?value) { if (value != i) { Debug.print("getOpt: Mismatch at index " # Nat.toText(i) # ": expected ?" # Nat.toText(i) # ", got ?" # Nat.toText(value)); @@ -1107,14 +1107,13 @@ func testGetOpt(n : Nat) : Bool { } }; - // Test out-of-bounds access - switch (List.getOpt(vec, n)) { - case (null) { - // This is expected - }; - case (?value) { - Debug.print("getOpt: Expected null for out-of-bounds access, got ?" # Nat.toText(value)); - return false + for (i in Nat.range(n, 3 * n + 3)) { + switch (List.getOpt(vec, i)) { + case (?value) { + Debug.print("getOpt: Unexpected value at index " # Nat.toText(i) # ": got ?" # Nat.toText(value)); + return false + }; + case (null) {} } }; From e4c94f9282830979b60bdae44a1bdeb948efd640 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 3 Jul 2025 16:54:19 +0300 Subject: [PATCH 069/123] Format tests, fix api. --- test/List.test.mo | 2 +- validation/api/api.lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/List.test.mo b/test/List.test.mo index 853264abd..7e4e784f1 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1283,7 +1283,7 @@ func runAllTests() { runTest("testFilterMap", testFilterMap); runTest("testPure", testPure); runTest("testReverseForEach", testReverseForEach); - runTest("testForEach", testForEach); + runTest("testForEach", testForEach) }; // Run all tests diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 8513293f3..f49cd55be 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -551,7 +551,7 @@ "public func reverseInPlace(list : List)", "public func reverseValues(list : List) : Iter.Iter", "public func singleton(element : T) : List", - "public func size(list : List) : Nat", + "public func size( list : { var blockIndex : Nat; var elementIndex : Nat } ) : Nat", "public func sort(list : List, compare : (T, T) -> Order.Order)", "public func toArray(list : List) : [T]", "public func toPure(list : List) : PureList.List", From 7edd240ada8e9577d1de9f4c272f7ac98c89175c Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 3 Jul 2025 16:57:42 +0300 Subject: [PATCH 070/123] Fix docs and formatting. --- src/List.mo | 10 +++++----- test/List.test.mo | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/List.mo b/src/List.mo index e32c0c0a2..33e9d1ad8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -313,7 +313,7 @@ module { /// ```motoko include=import /// import Nat "mo:core/Nat"; /// - /// let lists = List.fromArray>([ + /// let lists = List.fromArray>([ /// List.fromArray([0, 1, 2]), List.fromArray([2, 3]), List.fromArray([]), List.fromArray([4]) /// ]); /// let flatList = List.flatten(lists); @@ -701,7 +701,7 @@ module { /// /// let list = List.fromArray([1, 2, 3, 4]); /// let newList = List.flatMap(list, func x = [x, -x].vals()); - /// assert List.equal(newList, List.fromArray([1, -1, 2, -2, 3, -3, 4, -4]), Int.equal); + /// assert List.equal(newList, List.fromArray([1, -1, 2, -2, 3, -3, 4, -4]), Int.equal); /// ``` /// Runtime: O(size) /// @@ -1309,8 +1309,8 @@ module { /// ```motoko include=import /// import Char "mo:core/Char"; /// let list = List.fromArray(['c', 'o', 'f', 'f', 'e', 'e']); - /// assert List.prevIndexOf(list, 'c', list.size(), Char.equal) == ?0; - /// assert List.prevIndexOf(list, 'e', list.size(), Char.equal) == ?5; + /// assert List.prevIndexOf(list, 'c', List.size(list), Char.equal) == ?0; + /// assert List.prevIndexOf(list, 'e', List.size(list), Char.equal) == ?5; /// assert List.prevIndexOf(list, 'e', 5, Char.equal) == ?4; /// assert List.prevIndexOf(list, 'e', 4, Char.equal) == null; /// ``` @@ -2121,7 +2121,7 @@ module { /// /// ```motoko include=import /// let list = List.fromArray([1, 2, 3, 4, 5]); - /// let iter1 = List.range(list, 3, list.size()); + /// let iter1 = List.range(list, 3, List.size(list)); /// assert iter1.next() == ?4; /// assert iter1.next() == ?5; /// assert iter1.next() == null; diff --git a/test/List.test.mo b/test/List.test.mo index 1cb7e279a..95cc56860 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1631,7 +1631,7 @@ func testMin(n : Nat) : Bool { Debug.print("Min failed: expected ?0, got " # debug_show (min)); return false }; - List.put(vec, i, i + 1); + List.put(vec, i, i + 1) }; true }; @@ -1654,7 +1654,7 @@ func testMax(n : Nat) : Bool { Debug.print("Max failed: expected ?" # Nat.toText(n + 1) # ", got " # debug_show (max)); return false }; - List.put(vec, i, i + 1); + List.put(vec, i, i + 1) }; true }; From 577e8f46f249d31308b13937ef75a6ff1e90e404 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 3 Jul 2025 17:01:27 +0300 Subject: [PATCH 071/123] Fix api --- validation/api/api.lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index b8965a182..935eca0d1 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -566,7 +566,7 @@ "public func reverseValues(list : List) : Iter.Iter", "public func singleton(element : T) : List", "public func size( list : { var blockIndex : Nat; var elementIndex : Nat } ) : Nat", - "public func sliceToArray( list : { var blockIndex : Nat; var elementIndex : Nat } , fromInclusive : Int, toExclusive : Int) : [T]", + "public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", "public func sort(list : List, compare : (T, T) -> Order.Order) : List", "public func sortInPlace(list : List, compare : (T, T) -> Order.Order)", "public func tabulate(size : Nat, generator : Nat -> T) : List", From 035e39542ec3cd4c32e1adcf159ab92de3c52b52 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 3 Jul 2025 18:21:38 +0300 Subject: [PATCH 072/123] Added fill, truncate. --- src/List.mo | 76 +++++++++++++++++++++++++++++++++++++++++++++++ test/List.test.mo | 48 ++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/src/List.mo b/src/List.mo index 33e9d1ad8..10d477224 100644 --- a/src/List.mo +++ b/src/List.mo @@ -110,6 +110,39 @@ module { /// Space: `O(size)` public func repeat(initValue : T, size : Nat) : List = repeatInternal(?initValue, size); + /// Fills all elements in the list with the given value. + /// + /// Example: + /// ```motoko include=import + /// let list = List.fromArray([1, 2, 3]); + /// List.fill(list, 0); // fills the list with 0 + /// assert List.toArray(list) == [0, 0, 0]; + /// ``` + /// + /// Runtime: `O(size)` + /// + /// Space: `O(1)` + public func fill(list : List, value : T) { + let blocks = list.blocks; + let blockCount = blocks.size(); + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = if (i == blockIndex) elementIndex else db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + db[j] := ?value; + j += 1 + }; + i += 1 + } + }; + /// Converts a mutable `List` to a purely functional `PureList`. /// /// Example: @@ -233,6 +266,49 @@ module { /// Runtime: `O(count)` public func addRepeat(list : List, initValue : T, count : Nat) = addRepeatInternal(list, ?initValue, count); + /// Truncates the list to the specified size. + /// If the new size is larger than the current size, it will trap. + /// + /// Example: + /// ```motoko include=import + /// let list = List.fromArray([1, 2, 3, 4, 5]); + /// List.truncate(list, 3); // list is now [1, 2, 3] + /// assert List.toArray(list) == [1, 2, 3]; + /// ``` + /// + /// Runtime: `O(size)` + /// + /// Space: `O(1)` + public func truncate(list : List, newSize : Nat) { + if (newSize > size(list)) Prim.trap "List.truncate: newSize is larger than current size"; + + let (blockIndex, elementIndex) = locate(newSize); + list.blockIndex := blockIndex; + list.elementIndex := elementIndex; + let newBlocksCount = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + + let newBlocks = if (newBlocksCount < list.blocks.size()) { + let oldDataBlocks = list.blocks; + list.blocks := VarArray.tabulate<[var ?T]>(newBlocksCount, func(i) = oldDataBlocks[i]); + list.blocks + } else list.blocks; + + var i = if (elementIndex == 0) blockIndex else blockIndex + 1; + while (i < newBlocksCount) { + newBlocks[i] := [var]; + i += 1 + }; + if (elementIndex != 0) { + let block = newBlocks[blockIndex]; + var i = elementIndex; + var to = block.size(); + while (i < to) { + block[i] := null; + i += 1 + } + } + }; + /// Resets the list to size 0, de-referencing all elements. /// /// Example: diff --git a/test/List.test.mo b/test/List.test.mo index 95cc56860..ffc31b6f1 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1003,6 +1003,52 @@ func testInit(n : Nat) : Bool { true }; +func testFill(n : Nat) : Bool { + let vec = List.tabulate(n, func i = i + 1); + List.fill(vec, 42); + if (List.size(vec) != n) { + Debug.print("Fill failed: expected size " # Nat.toText(n) # ", got " # Nat.toText(List.size(vec))); + return false + }; + if (not List.all(vec, func x = x == 42)) { + Debug.print("Fill failed"); + return false + }; + true +}; + +func testTruncate(n : Nat) : Bool { + for (i in Nat.range(0, n + 1)) { + let vec = List.tabulate(n, func j = j); + List.truncate(vec, i); + if (List.size(vec) != i) { + Debug.print("Truncate failed: expected size " # Nat.toText(i) # ", got " # Nat.toText(List.size(vec))); + return false + }; + for (j in Nat.range(0, i)) { + if (List.get(vec, j) != j) { + Debug.print("Truncate failed at index " # Nat.toText(j) # ": expected " # Nat.toText(j) # ", got " # Nat.toText(List.get(vec, j))); + return false + } + }; + let b = vec.blockIndex; + let e = vec.elementIndex; + let blocks = vec.blocks; + if (b < blocks.size()) { + let db = blocks[b]; + var i = e; + while (i < db.size()) { + if (db[i] != null) { + Debug.print("Truncate failed: expected null at index " # Nat.toText(i) # ", got " # debug_show (db[i])); + return false + }; + i += 1 + } + } + }; + true +}; + func testAdd(n : Nat) : Bool { if (n == 0) return true; let vec = List.empty(); @@ -1663,6 +1709,8 @@ func testMax(n : Nat) : Bool { func runAllTests() { runTest("testNew", testNew); runTest("testInit", testInit); + runTest("testFill", testFill); + runTest("testTruncate", testTruncate); runTest("testAdd", testAdd); runTest("testAddAll", testAddAll); runTest("testRemoveLast", testRemoveLast); From 2d40c3b0b18ae2d5fe8ee3c0e8cb42f9f77c881c Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 5 Jul 2025 15:34:52 +0300 Subject: [PATCH 073/123] Added retain, append. --- src/List.mo | 113 ++++++++++++++++++++++++++++++++--- test/List.test.mo | 41 +++++++++++++ validation/api/api.lock.json | 4 ++ 3 files changed, 149 insertions(+), 9 deletions(-) diff --git a/src/List.mo b/src/List.mo index 10d477224..9952bcd5e 100644 --- a/src/List.mo +++ b/src/List.mo @@ -254,6 +254,16 @@ module { } }; + private func reserve(list : List, size : Nat) { + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + addRepeatInternal(list, null, size); + + list.blockIndex := blockIndex; + list.elementIndex := elementIndex + }; + /// Add to list `count` copies of the initial value. /// /// ```motoko include=import @@ -440,15 +450,8 @@ module { public func join(lists : Iter.Iter>) : List { var result = empty(); for (list in lists) { - let oldBlockIndex = result.blockIndex; - let oldElementIndex = result.elementIndex; - - addRepeatInternal(result, null, size(list)); - - result.blockIndex := oldBlockIndex; - result.elementIndex := oldElementIndex; - - forEach(list, func item = add(result, item)) + reserve(result, size(list)); + forEach(list, func item = addUnsafe(result, item)) }; result }; @@ -725,6 +728,46 @@ module { filtered }; + /// Retains only the elements in `list` for which the predicate returns true. + /// Modifies the original list in place. + /// + /// Example: + /// ```motoko include=import + /// let list = List.fromArray([1, 2, 3, 4]); + /// List.retain(list, func x = x % 2 == 0); + /// assert List.toArray(list) == [2, 4]; + /// ``` + /// + /// Runtime: `O(size)` + /// + /// Space: `O(sqrt(size))` if `list` was truncated otherwise `O(1)` + public func retain(list : List, predicate : T -> Bool) { + list.blockIndex := 1; + list.elementIndex := 0; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (predicate(x)) addUnsafe(list, x); + case null return + }; + j += 1 + }; + i += 1 + }; + + truncate(list, size(list)) + }; + /// Returns a new list containing all elements from `list` for which the function returns ?element. /// Discards all elements for which the function returns null. /// @@ -936,6 +979,19 @@ module { list.elementIndex := elementIndex }; + private func addUnsafe(list : List, element : T) { + var elementIndex = list.elementIndex; + let lastDataBlock = list.blocks[list.blockIndex]; + lastDataBlock[elementIndex] := ?element; + + elementIndex += 1; + if (elementIndex == lastDataBlock.size()) { + elementIndex := 0; + list.blockIndex += 1 + }; + list.elementIndex := elementIndex + }; + /// Removes and returns the last item in the list or `null` if /// the list is empty. /// @@ -1863,6 +1919,45 @@ module { for (element in iter) add(list, element) }; + /// Appends all elements from `added` to the end of `list`. + /// + /// Example: + /// ```motoko include=import + /// import Nat "mo:core/Nat"; + /// + /// let list = List.fromArray([1, 2]); + /// let added = List.fromArray([3, 4]); + /// List.append(list, added); + /// assert List.toArray(list) == [1, 2, 3, 4]; + /// ``` + /// + /// Runtime: `O(size(added))` + /// + /// Space: `O(size(added))` + public func append(list : List, added : List) { + reserve(list, size(added)); + + let blocks = added.blocks; + let blockCount = blocks.size(); + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) addUnsafe(list, x); + case null return + }; + j += 1 + }; + i += 1 + } + }; + /// Creates a new immutable array containing all elements from the list. /// Elements appear in the same order as in the list. /// diff --git a/test/List.test.mo b/test/List.test.mo index ffc31b6f1..8e0f3be63 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1090,6 +1090,26 @@ func testAddAll(n : Nat) : Bool { true }; +func testAppend(n : Nat) : Bool { + if (n > 10) return true; + + for (i in Nat.range(0, n + 1)) { + for (j in Nat.range(0, n + 1)) { + let first = List.tabulate(i, func x = x); + let second = List.tabulate(j, func x = x); + let sum = List.concat([first, second]); + List.append(first, second); + + if (not List.equal(first, sum, Nat.equal)) { + Debug.print("Append failed for " # List.toText(first, Nat.toText) # " and " # List.toText(second, Nat.toText)); + return false + } + } + }; + + true +}; + func testRemoveLast(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); var i = n; @@ -1460,6 +1480,25 @@ func testFilter(n : Nat) : Bool { true }; +func testRetain(n : Nat) : Bool { + if (n > 10) return true; + + for (mod in Nat.range(1, n + 1)) { + for (rem in Nat.range(0, mod + 1)) { + let f : Nat -> Bool = func x = x % mod == rem; + let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + let expected = List.filter(vec, f); + List.retain(vec, f); + if (not List.equal(vec, expected, Nat.equal)) { + Debug.print("Retain failed for mod " # Nat.toText(mod) # " and rem " # Nat.toText(rem) # ""); + return false + } + } + }; + + true +}; + func testFilterMap(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); @@ -1713,6 +1752,7 @@ func runAllTests() { runTest("testTruncate", testTruncate); runTest("testAdd", testAdd); runTest("testAddAll", testAddAll); + runTest("testAppend", testAppend); runTest("testRemoveLast", testRemoveLast); runTest("testGet", testGet); runTest("testGetOpt", testGetOpt); @@ -1739,6 +1779,7 @@ func runAllTests() { runTest("testFoldLeft", testFoldLeft); runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); + runTest("testRetain", testRetain); runTest("testFilterMap", testFilterMap); runTest("testPure", testPure); runTest("testReverseForEach", testReverseForEach); diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 935eca0d1..c7e995da1 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -509,6 +509,7 @@ "public func addRepeat(list : List, initValue : T, count : Nat)", "public func all(list : List, predicate : T -> Bool) : Bool", "public func any(list : List, predicate : T -> Bool) : Bool", + "public func append(list : List, added : List)", "public func clear(list : List)", "public func clone(list : List) : List", "public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order", @@ -518,6 +519,7 @@ "public func empty() : List", "public func enumerate(list : List) : Iter.Iter<(Nat, T)>", "public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool", + "public func fill(list : List, value : T)", "public func filter(list : List, predicate : T -> Bool) : List", "public func filterMap(list : List, f : T -> ?R) : List", "public func find(list : List, predicate : T -> Bool) : ?T", @@ -558,6 +560,7 @@ "public func remove(list : List, index : Nat) : T", "public func removeLast(list : List) : ?T", "public func repeat(initValue : T, size : Nat) : List", + "public func retain(list : List, predicate : T -> Bool)", "public func reverse(list : List) : List", "public func reverseEnumerate(list : List) : Iter.Iter<(Nat, T)>", "public func reverseForEach(list : List, f : T -> ())", @@ -574,6 +577,7 @@ "public func toPure(list : List) : PureList.List", "public func toText(list : List, f : T -> Text) : Text", "public func toVarArray(list : List) : [var T]", + "public func truncate(list : List, newSize : Nat)", "public func values(list : List) : Iter.Iter" ] }, From 3ec3ef9cfc54a4f078725c824bb9ce56f40baab4 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 5 Jul 2025 15:46:54 +0300 Subject: [PATCH 074/123] Optimize isEmpty --- src/List.mo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/List.mo b/src/List.mo index 709fbd2c8..efde6893b 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1457,7 +1457,7 @@ module { /// /// Space: `O(1)` public func first(list : List) : ?T { - if (list.blockIndex == 1 and list.elementIndex == 0) null else list.blocks[1][0] + if (list.blockIndex == 1) null else list.blocks[1][0] }; /// Returns the last element of `list`. Traps if `list` is empty. @@ -2147,7 +2147,7 @@ module { /// /// Space: `O(1)` public func isEmpty(list : List) : Bool { - list.blockIndex == 1 and list.elementIndex == 0 + list.blockIndex == 1 }; } From 121974d636828d2fd33bb6636a3fd99ef432f45d Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 5 Jul 2025 17:36:05 +0300 Subject: [PATCH 075/123] Added isSorted, deduplicate --- src/List.mo | 123 +++++++++++++++++++++++++++++++++++++++------- test/List.test.mo | 38 ++++++++++++++ 2 files changed, 144 insertions(+), 17 deletions(-) diff --git a/src/List.mo b/src/List.mo index c5366465f..16ce18110 100644 --- a/src/List.mo +++ b/src/List.mo @@ -742,29 +742,33 @@ module { /// /// Space: `O(sqrt(size))` if `list` was truncated otherwise `O(1)` public func retain(list : List, predicate : T -> Bool) { - list.blockIndex := 1; - list.elementIndex := 0; + func retainInternal(list : List, predicate : T -> Bool) { + list.blockIndex := 1; + list.elementIndex := 0; - let blocks = list.blocks; - let blockCount = blocks.size(); + let blocks = list.blocks; + let blockCount = blocks.size(); - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return; + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) if (predicate(x)) addUnsafe(list, x); - case null return + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (predicate(x)) addUnsafe(list, x); + case null return + }; + j += 1 }; - j += 1 - }; - i += 1 + i += 1 + } }; + retainInternal(list, predicate); + truncate(list, size(list)) }; @@ -1213,6 +1217,91 @@ module { fromVarArray(array) }; + /// Checks whether the `list` is sorted. + /// + /// Example: + /// import Nat "mo:core/Nat"; + /// + /// let list = List.fromArray([1, 2, 3]); + /// assert List.isSorted(list); + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + public func isSorted(list : List, compare : (T, T) -> Order.Order) : Bool { + var prev = switch (first(list)) { + case (?x) x; + case _ return true + }; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 2; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return true; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) switch (compare(x, prev)) { + case (#greater or #equal) prev := x; + case (#less) return false + }; + case null return true + }; + j += 1 + }; + i += 1 + }; + + true + }; + + public func deduplicate(list : List, equal : (T, T) -> Bool) { + func deduplicateInternal(list : List, equal : (T, T) -> Bool) { + var prev = switch (first(list)) { + case (?x) x; + case _ return + }; + + list.blockIndex := 1; + list.elementIndex := 0; + + addUnsafe(list, prev); + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = 2; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) { + if (not equal(x, prev)) addUnsafe(list, x); + prev := x + }; + case null return + }; + j += 1 + }; + i += 1 + } + }; + + deduplicateInternal(list, equal); + + truncate(list, size(list)) + }; + /// Inserts `element` at `index` in the list, shifting existing elements to the right. /// Traps if `index > size`. Indexing is zero-based. /// diff --git a/test/List.test.mo b/test/List.test.mo index 8e0f3be63..0b01315ef 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1366,6 +1366,42 @@ func testSort(n : Nat) : Bool { List.equal(vec, List.fromArray(Array.sort(array, Int.compare)), Int.equal) }; +func testIsSorted(n : Nat) : Bool { + let sorted = List.tabulate(n, func i = i); + if (not List.isSorted(sorted, Nat.compare)) { + Debug.print("isSorted fails on " # List.toText(sorted, Nat.toText)); + return false + }; + + let notSorted = List.tabulate(n, func i = n - i - 1); + if (List.size(notSorted) >= 2 and List.isSorted(notSorted, Nat.compare)) { + Debug.print("isSorted fails on " # List.toText(notSorted, Nat.toText)); + return false + }; + + true +}; + +func testDeduplicate(n : Nat) : Bool { + if (n != 0) return true; + + let lists = [ + List.fromArray([1, 1, 2, 2, 3, 3]), + List.fromArray([1, 2, 3]), + List.fromArray([1, 1, 2, 3]) + ]; + + for (list in lists.vals()) { + List.deduplicate(list, Nat.equal); + if (not List.equal(list, List.fromArray([1, 2, 3]), Nat.equal)) { + Debug.print("Deduplicate failed for " # List.toText(list, Nat.toText)); + return false + } + }; + + true +}; + func testToArray(n : Nat) : Bool { let array = Array.tabulate(n, func(i) = i); let vec = List.fromArray(array); @@ -1770,6 +1806,8 @@ func runAllTests() { runTest("testContains", testContains); runTest("testReverse", testReverse); runTest("testSort", testSort); + runTest("testIsSorted", testIsSorted); + runTest("testDeduplicate", testDeduplicate); runTest("testToArray", testToArray); runTest("testToVarArray", testToVarArray); runTest("testFromVarArray", testFromVarArray); From 350ec252e3ac2e2f26fa2f3bc0b04208be45a8f7 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 5 Jul 2025 17:50:11 +0300 Subject: [PATCH 076/123] Added comment. --- src/List.mo | 15 +++++++++++++++ validation/api/api.lock.json | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/List.mo b/src/List.mo index 16ce18110..68f80c773 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1220,6 +1220,7 @@ module { /// Checks whether the `list` is sorted. /// /// Example: + /// ``` /// import Nat "mo:core/Nat"; /// /// let list = List.fromArray([1, 2, 3]); @@ -1261,6 +1262,20 @@ module { true }; + /// Remove adjacent duplicates from the `list`, if the `list` is sorted all elements will be unique. + /// + /// Example: + /// ``` + /// import Nat "mo:core/Nat"; + /// + /// let list = List.fromArray([1, 1, 2, 2, 3]); + /// List.deduplicate(list, Nat.equal); + /// assert List.equal(list, List.fromArray([1, 2, 3]), Nat.equal); + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) public func deduplicate(list : List, equal : (T, T) -> Bool) { func deduplicateInternal(list : List, equal : (T, T) -> Bool) { var prev = switch (first(list)) { diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index c7e995da1..29852076d 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -516,6 +516,7 @@ "public func concat(lists : [List]) : List", "public func concatSlices(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List", "public func contains(list : List, equal : (T, T) -> Bool, element : T) : Bool", + "public func deduplicate(list : List, equal : (T, T) -> Bool)", "public func empty() : List", "public func enumerate(list : List) : Iter.Iter<(Nat, T)>", "public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool", @@ -542,6 +543,7 @@ "public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat", "public func insert(list : List, index : Nat, element : T)", "public func isEmpty(list : List) : Bool", + "public func isSorted(list : List, compare : (T, T) -> Order.Order) : Bool", "public func join(lists : Iter.Iter>) : List", "public func keys(list : List) : Iter.Iter", "public func last(list : List) : ?T", From 84fda614e664e172e48a9d655c229aca6c8a431b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 5 Jul 2025 23:55:47 +0300 Subject: [PATCH 077/123] Refactor addRepeat. --- src/List.mo | 54 +++++++++++++++++++++++++++-------------------- test/List.test.mo | 35 ++++++++++++++++++------------ 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/List.mo b/src/List.mo index efde6893b..a131d0f0c 100644 --- a/src/List.mo +++ b/src/List.mo @@ -172,35 +172,40 @@ module { }; private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { - let (blockIndex, elementIndex) = locate(size(list) + count); - let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let (b, e) = locate(size(list) + count); + let blocksCount = newIndexBlockLength(Nat32.fromNat(if (e == 0) b - 1 else b)); - let oldBlocks = list.blocks.size(); - if (oldBlocks < blocks) { - let oldDataBlocks = list.blocks; - list.blocks := VarArray.repeat<[var ?T]>([var], blocks); + let oldBlocksCount = list.blocks.size(); + if (oldBlocksCount < blocksCount) { + let oldBlocks = list.blocks; + let blocks = VarArray.repeat<[var ?T]>([var], blocksCount); var i = 0; - while (i < oldBlocks) { - list.blocks[i] := oldDataBlocks[i]; + while (i < oldBlocksCount) { + blocks[i] := oldBlocks[i]; i += 1 - } + }; + list.blocks := blocks; }; + let blocks = list.blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex; + var cnt = count; while (cnt > 0) { - let dbSize = dataBlockSize(list.blockIndex); - if (list.elementIndex == 0 and dbSize <= cnt) { - list.blocks[list.blockIndex] := VarArray.repeat(initValue, dbSize); + let dbSize = dataBlockSize(blockIndex); + if (elementIndex == 0 and dbSize <= cnt) { + blocks[blockIndex] := VarArray.repeat(initValue, dbSize); cnt -= dbSize; - list.blockIndex += 1 + blockIndex += 1 } else { - if (list.blocks[list.blockIndex].size() == 0) { - list.blocks[list.blockIndex] := VarArray.repeat(null, dbSize) + if (blocks[blockIndex].size() == 0) { + blocks[blockIndex] := VarArray.repeat(null, dbSize) }; - let from = list.elementIndex; - let to = Nat.min(list.elementIndex + cnt, dbSize); + let from = elementIndex; + let to = Nat.min(elementIndex + cnt, dbSize); - let block = list.blocks[list.blockIndex]; + let block = blocks[blockIndex]; if (not Option.isNull(initValue)) { var i = from; while (i < to) { @@ -209,14 +214,17 @@ module { } }; - list.elementIndex := to; - if (list.elementIndex == dbSize) { - list.elementIndex := 0; - list.blockIndex += 1 + elementIndex := to; + if (elementIndex == dbSize) { + elementIndex := 0; + blockIndex += 1 }; cnt -= to - from } - } + }; + + list.blockIndex := blockIndex; + list.elementIndex := elementIndex; }; /// Add to list `count` copies of the initial value. diff --git a/test/List.test.mo b/test/List.test.mo index 7e4e784f1..af0071669 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -946,21 +946,28 @@ func testAdd(n : Nat) : Bool { true }; -func testAddAll(n : Nat) : Bool { - if (n == 0) return true; - let vec = List.repeat(0, n); - List.addRepeat(vec, 1, n); - if (List.size(vec) != 2 * n) { - Debug.print("Size mismatch: expected " # Nat.toText(2 * n) # ", got " # Nat.toText(List.size(vec))); - return false - }; - for (i in Nat.range(0, n)) { - let value = List.get(vec, n + i); - if (value != 1) { - Debug.print("Value mismatch at index " # Nat.toText(i) # ": expected " # Nat.toText(1) # ", got " # Nat.toText(value)); - return false +func testAddRepeat(n : Nat) : Bool { + if (n > 10) return true; + + for (i in Nat.range(0, n + 1)) { + for (j in Nat.range(0, n + 1)) { + let vec = List.repeat(0, i); + List.addRepeat(vec, 1, j); + if (List.size(vec) != i + j) { + Debug.print("Size mismatch: expected " # Nat.toText(i + j) # ", got " # Nat.toText(List.size(vec))); + return false + }; + for (k in Nat.range(0, i + j)) { + let expected = if (k < i) 0 else 1; + let got = List.get(vec, k); + if (expected != got) { + Debug.print("addRepat failed i = " # Nat.toText(i) # " j = " # Nat.toText(j) # " k = " # Nat.toText(k) # " expected = " # Nat.toText(expected) # " got = " # Nat.toText(got)); + return false + } + } } }; + true }; @@ -1262,7 +1269,7 @@ func runAllTests() { runTest("testNew", testNew); runTest("testInit", testInit); runTest("testAdd", testAdd); - runTest("testAddAll", testAddAll); + runTest("testAddRepeat", testAddRepeat); runTest("testRemoveLast", testRemoveLast); runTest("testGet", testGet); runTest("testGetOpt", testGetOpt); From 51e58595c2431dae72ce561b7e68777e8e7ac301 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 6 Jul 2025 20:11:10 +0300 Subject: [PATCH 078/123] Added fillWith. --- src/List.mo | 40 ++++++++++++++++++++++++++++++++++++++-- test/List.test.mo | 11 +++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/List.mo b/src/List.mo index af06cd509..a71668ec1 100644 --- a/src/List.mo +++ b/src/List.mo @@ -143,6 +143,42 @@ module { } }; + /// Fills all elements in the list with the given closure. + /// + /// Example: + /// ```motoko include=import + /// let list = List.fromArray([0, 0, 0]); + /// List.fillWith(list, func i = i + 1); + /// assert List.toArray(list) == [1, 2, 3]; + /// ``` + /// + /// Runtime: `O(size)` + /// + /// Space: `O(1)` + public func fillWith(list : List, generator : Nat -> T) { + var index = 0; + + let blocks = list.blocks; + let blockCount = blocks.size(); + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = if (i == blockIndex) elementIndex else db.size(); + if (sz == 0) return; + + var j = 0; + while (j < sz) { + db[j] := ?generator(index); + j += 1; + index += 1 + }; + i += 1 + } + }; + /// Converts a mutable `List` to a purely functional `PureList`. /// /// Example: @@ -219,7 +255,7 @@ module { blocks[i] := oldBlocks[i]; i += 1 }; - list.blocks := blocks; + list.blocks := blocks }; let blocks = list.blocks; @@ -259,7 +295,7 @@ module { }; list.blockIndex := blockIndex; - list.elementIndex := elementIndex; + list.elementIndex := elementIndex }; private func reserve(list : List, size : Nat) { diff --git a/test/List.test.mo b/test/List.test.mo index ebf9c6c02..fe6a203d8 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1017,6 +1017,16 @@ func testFill(n : Nat) : Bool { true }; +func testFillWith(n : Nat) : Bool { + let vec = List.repeat(0, n); + List.fillWith(vec, func i = i + 1); + if (not List.equal(vec, List.tabulate(n, func i = i + 1), Nat.equal)) { + Debug.print("Got = " # List.toText(vec, Nat.toText)); + return false + }; + true +}; + func testTruncate(n : Nat) : Bool { for (i in Nat.range(0, n + 1)) { let vec = List.tabulate(n, func j = j); @@ -1792,6 +1802,7 @@ func runAllTests() { runTest("testNew", testNew); runTest("testInit", testInit); runTest("testFill", testFill); + runTest("testFillWith", testFillWith); runTest("testTruncate", testTruncate); runTest("testAdd", testAdd); runTest("testAddRepeat", testAddRepeat); From e85bd81353c851ce3bca06cbf1b5b9394b91835b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 6 Jul 2025 20:21:12 +0300 Subject: [PATCH 079/123] Remove redundant methods. --- src/List.mo | 398 ----------------------------------- test/List.test.mo | 309 --------------------------- validation/api/api.lock.json | 9 - 3 files changed, 716 deletions(-) diff --git a/src/List.mo b/src/List.mo index a71668ec1..fbabeb631 100644 --- a/src/List.mo +++ b/src/List.mo @@ -110,75 +110,6 @@ module { /// Space: `O(size)` public func repeat(initValue : T, size : Nat) : List = repeatInternal(?initValue, size); - /// Fills all elements in the list with the given value. - /// - /// Example: - /// ```motoko include=import - /// let list = List.fromArray([1, 2, 3]); - /// List.fill(list, 0); // fills the list with 0 - /// assert List.toArray(list) == [0, 0, 0]; - /// ``` - /// - /// Runtime: `O(size)` - /// - /// Space: `O(1)` - public func fill(list : List, value : T) { - let blocks = list.blocks; - let blockCount = blocks.size(); - let blockIndex = list.blockIndex; - let elementIndex = list.elementIndex; - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = if (i == blockIndex) elementIndex else db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - db[j] := ?value; - j += 1 - }; - i += 1 - } - }; - - /// Fills all elements in the list with the given closure. - /// - /// Example: - /// ```motoko include=import - /// let list = List.fromArray([0, 0, 0]); - /// List.fillWith(list, func i = i + 1); - /// assert List.toArray(list) == [1, 2, 3]; - /// ``` - /// - /// Runtime: `O(size)` - /// - /// Space: `O(1)` - public func fillWith(list : List, generator : Nat -> T) { - var index = 0; - - let blocks = list.blocks; - let blockCount = blocks.size(); - let blockIndex = list.blockIndex; - let elementIndex = list.elementIndex; - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = if (i == blockIndex) elementIndex else db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - db[j] := ?generator(index); - j += 1; - index += 1 - }; - i += 1 - } - }; - /// Converts a mutable `List` to a purely functional `PureList`. /// /// Example: @@ -320,49 +251,6 @@ module { /// Runtime: `O(count)` public func addRepeat(list : List, initValue : T, count : Nat) = addRepeatInternal(list, ?initValue, count); - /// Truncates the list to the specified size. - /// If the new size is larger than the current size, it will trap. - /// - /// Example: - /// ```motoko include=import - /// let list = List.fromArray([1, 2, 3, 4, 5]); - /// List.truncate(list, 3); // list is now [1, 2, 3] - /// assert List.toArray(list) == [1, 2, 3]; - /// ``` - /// - /// Runtime: `O(size)` - /// - /// Space: `O(1)` - public func truncate(list : List, newSize : Nat) { - if (newSize > size(list)) Prim.trap "List.truncate: newSize is larger than current size"; - - let (blockIndex, elementIndex) = locate(newSize); - list.blockIndex := blockIndex; - list.elementIndex := elementIndex; - let newBlocksCount = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); - - let newBlocks = if (newBlocksCount < list.blocks.size()) { - let oldDataBlocks = list.blocks; - list.blocks := VarArray.tabulate<[var ?T]>(newBlocksCount, func(i) = oldDataBlocks[i]); - list.blocks - } else list.blocks; - - var i = if (elementIndex == 0) blockIndex else blockIndex + 1; - while (i < newBlocksCount) { - newBlocks[i] := [var]; - i += 1 - }; - if (elementIndex != 0) { - let block = newBlocks[blockIndex]; - var i = elementIndex; - var to = block.size(); - while (i < to) { - block[i] := null; - i += 1 - } - } - }; - /// Resets the list to size 0, de-referencing all elements. /// /// Example: @@ -772,50 +660,6 @@ module { filtered }; - /// Retains only the elements in `list` for which the predicate returns true. - /// Modifies the original list in place. - /// - /// Example: - /// ```motoko include=import - /// let list = List.fromArray([1, 2, 3, 4]); - /// List.retain(list, func x = x % 2 == 0); - /// assert List.toArray(list) == [2, 4]; - /// ``` - /// - /// Runtime: `O(size)` - /// - /// Space: `O(sqrt(size))` if `list` was truncated otherwise `O(1)` - public func retain(list : List, predicate : T -> Bool) { - func retainInternal(list : List, predicate : T -> Bool) { - list.blockIndex := 1; - list.elementIndex := 0; - - let blocks = list.blocks; - let blockCount = blocks.size(); - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) if (predicate(x)) addUnsafe(list, x); - case null return - }; - j += 1 - }; - i += 1 - } - }; - - retainInternal(list, predicate); - - truncate(list, size(list)) - }; - /// Returns a new list containing all elements from `list` for which the function returns ?element. /// Discards all elements for which the function returns null. /// @@ -1261,106 +1105,6 @@ module { fromVarArray(array) }; - /// Checks whether the `list` is sorted. - /// - /// Example: - /// ``` - /// import Nat "mo:core/Nat"; - /// - /// let list = List.fromArray([1, 2, 3]); - /// assert List.isSorted(list); - /// ``` - /// - /// Runtime: O(size) - /// - /// Space: O(1) - public func isSorted(list : List, compare : (T, T) -> Order.Order) : Bool { - var prev = switch (first(list)) { - case (?x) x; - case _ return true - }; - - let blocks = list.blocks; - let blockCount = blocks.size(); - - var i = 2; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return true; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) switch (compare(x, prev)) { - case (#greater or #equal) prev := x; - case (#less) return false - }; - case null return true - }; - j += 1 - }; - i += 1 - }; - - true - }; - - /// Remove adjacent duplicates from the `list`, if the `list` is sorted all elements will be unique. - /// - /// Example: - /// ``` - /// import Nat "mo:core/Nat"; - /// - /// let list = List.fromArray([1, 1, 2, 2, 3]); - /// List.deduplicate(list, Nat.equal); - /// assert List.equal(list, List.fromArray([1, 2, 3]), Nat.equal); - /// ``` - /// - /// Runtime: O(size) - /// - /// Space: O(1) - public func deduplicate(list : List, equal : (T, T) -> Bool) { - func deduplicateInternal(list : List, equal : (T, T) -> Bool) { - var prev = switch (first(list)) { - case (?x) x; - case _ return - }; - - list.blockIndex := 1; - list.elementIndex := 0; - - addUnsafe(list, prev); - - let blocks = list.blocks; - let blockCount = blocks.size(); - - var i = 2; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) { - if (not equal(x, prev)) addUnsafe(list, x); - prev := x - }; - case null return - }; - j += 1 - }; - i += 1 - } - }; - - deduplicateInternal(list, equal); - - truncate(list, size(list)) - }; - /// Inserts `element` at `index` in the list, shifting existing elements to the right. /// Traps if `index > size`. Indexing is zero-based. /// @@ -2067,45 +1811,6 @@ module { for (element in iter) add(list, element) }; - /// Appends all elements from `added` to the end of `list`. - /// - /// Example: - /// ```motoko include=import - /// import Nat "mo:core/Nat"; - /// - /// let list = List.fromArray([1, 2]); - /// let added = List.fromArray([3, 4]); - /// List.append(list, added); - /// assert List.toArray(list) == [1, 2, 3, 4]; - /// ``` - /// - /// Runtime: `O(size(added))` - /// - /// Space: `O(size(added))` - public func append(list : List, added : List) { - reserve(list, size(added)); - - let blocks = added.blocks; - let blockCount = blocks.size(); - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) addUnsafe(list, x); - case null return - }; - j += 1 - }; - i += 1 - } - }; - /// Creates a new immutable array containing all elements from the list. /// Elements appear in the same order as in the list. /// @@ -2488,43 +2193,6 @@ module { } }; - public func forEachRange(list : List, f : T -> (), fromInclusive : Nat, toExclusive : Nat) { - if (not (fromInclusive <= toExclusive and toExclusive <= size(list))) Prim.trap("Invalid range"); - - func traverseBlock(block : [var ?T], f : T -> (), from : Nat, to : Nat) { - var i = from; - while (i < to) { - switch (block[i]) { - case (?value) f(value); - case null Prim.trap(INTERNAL_ERROR) - }; - i += 1 - } - }; - - let (fromBlock, fromElement) = locate(fromInclusive); - let (toBlock, toElement) = locate(toExclusive); - - let blocks = list.blocks; - let sz = blocks.size(); - - if (fromBlock == toBlock) { - if (fromBlock < sz) traverseBlock(blocks[fromBlock], f, fromElement, toElement); - return - }; - - traverseBlock(blocks[fromBlock], f, fromElement, blocks[fromBlock].size()); - - var i = fromBlock + 1; - let to = Nat.min(toBlock, sz); - while (i < to) { - traverseBlock(blocks[i], f, 0, blocks[i].size()); - i += 1 - }; - - if (toBlock < sz) traverseBlock(blocks[toBlock], f, 0, toElement) - }; - /// Returns a new array containing elements from `list` starting at index `fromInclusive` up to (but not including) index `toExclusive`. /// If the indices are out of bounds, they are clamped to the array bounds. /// @@ -3156,70 +2824,4 @@ module { list.blockIndex == 1 }; - /// Concatenates the provided slices into a new list. - /// Each slice is a tuple of a list, a starting index (inclusive), and an ending index (exclusive). - /// - /// Example: - /// ```motoko include=import - /// import Nat "mo:core/Nat"; - /// import Iter "mo:core/Iter"; - /// - /// let list1 = List.fromArray([1,2,3]); - /// let list2 = List.fromArray([4,5,6]); - /// let result = List.concatSlices([(list1, 0, 2), (list2, 1, 3)]); - /// assert Iter.toArray(List.values(result)) == [1,2,5,6]; - /// ``` - /// - /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all slices. - /// - /// Space: `O(sum_size)` - public func concatSlices(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List { - var length = 0; - for (slice in slices.vals()) { - let (list, start, end) = slice; - let sz = size(list); - let ok = start <= end and end <= sz; - if (not ok) { - Runtime.trap("Invalid slice in concat") - }; - length += end - start - }; - - var result = repeatInternal(null, length); - result.blockIndex := 1; - result.elementIndex := 0; - - for (slice in slices.vals()) { - let (list, start, end) = slice; - forEachRange( - list, - func(value) = add(result, value), - start, - end - ) - }; - - result - }; - - /// Concatenates the provided lists into a new list. - /// - /// Example: - /// ```motoko include=import - /// import Nat "mo:core/Nat"; - /// import Iter "mo:core/Iter"; - /// - /// let list1 = List.fromArray([1, 2, 3]); - /// let list2 = List.fromArray([4, 5, 6]); - /// let result = List.concat([list1, list2]); - /// assert Iter.toArray(List.values(result)) == [1, 2, 3, 4, 5, 6]; - /// ``` - /// - /// Runtime: `O(sum_size)` where `sum_size` is the sum of the sizes of all lists. - /// - /// Space: `O(sum_size)` - public func concat(lists : [List]) : List { - concatSlices(Array.tabulate<(List, Nat, Nat)>(lists.size(), func(i) = (lists[i], 0, size(lists[i])))) - }; - } diff --git a/test/List.test.mo b/test/List.test.mo index fe6a203d8..a1f8e7bca 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1003,62 +1003,6 @@ func testInit(n : Nat) : Bool { true }; -func testFill(n : Nat) : Bool { - let vec = List.tabulate(n, func i = i + 1); - List.fill(vec, 42); - if (List.size(vec) != n) { - Debug.print("Fill failed: expected size " # Nat.toText(n) # ", got " # Nat.toText(List.size(vec))); - return false - }; - if (not List.all(vec, func x = x == 42)) { - Debug.print("Fill failed"); - return false - }; - true -}; - -func testFillWith(n : Nat) : Bool { - let vec = List.repeat(0, n); - List.fillWith(vec, func i = i + 1); - if (not List.equal(vec, List.tabulate(n, func i = i + 1), Nat.equal)) { - Debug.print("Got = " # List.toText(vec, Nat.toText)); - return false - }; - true -}; - -func testTruncate(n : Nat) : Bool { - for (i in Nat.range(0, n + 1)) { - let vec = List.tabulate(n, func j = j); - List.truncate(vec, i); - if (List.size(vec) != i) { - Debug.print("Truncate failed: expected size " # Nat.toText(i) # ", got " # Nat.toText(List.size(vec))); - return false - }; - for (j in Nat.range(0, i)) { - if (List.get(vec, j) != j) { - Debug.print("Truncate failed at index " # Nat.toText(j) # ": expected " # Nat.toText(j) # ", got " # Nat.toText(List.get(vec, j))); - return false - } - }; - let b = vec.blockIndex; - let e = vec.elementIndex; - let blocks = vec.blocks; - if (b < blocks.size()) { - let db = blocks[b]; - var i = e; - while (i < db.size()) { - if (db[i] != null) { - Debug.print("Truncate failed: expected null at index " # Nat.toText(i) # ", got " # debug_show (db[i])); - return false - }; - i += 1 - } - } - }; - true -}; - func testAdd(n : Nat) : Bool { if (n == 0) return true; let vec = List.empty(); @@ -1107,26 +1051,6 @@ func testAddRepeat(n : Nat) : Bool { true }; -func testAppend(n : Nat) : Bool { - if (n > 10) return true; - - for (i in Nat.range(0, n + 1)) { - for (j in Nat.range(0, n + 1)) { - let first = List.tabulate(i, func x = x); - let second = List.tabulate(j, func x = x); - let sum = List.concat([first, second]); - List.append(first, second); - - if (not List.equal(first, sum, Nat.equal)) { - Debug.print("Append failed for " # List.toText(first, Nat.toText) # " and " # List.toText(second, Nat.toText)); - return false - } - } - }; - - true -}; - func testRemoveLast(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); var i = n; @@ -1290,26 +1214,6 @@ func testSliceToArray(n : Nat) : Bool { true }; -func testForEachRange(n : Nat) : Bool { - if (n > 10) return true; // Skip large ranges for performance - let vec = List.fromArray(Array.tabulate(n, func(i) = i)); - - for (left in Nat.range(0, n)) { - for (right in Nat.range(left, n + 1)) { - let expected = VarArray.tabulate(right - left, func(i) = left + i); - let result = VarArray.repeat(0, right - left); - List.forEachRange(vec, func(i) = result[i - left] := i, left, right); - if (Array.fromVarArray(result) != Array.fromVarArray(expected)) { - Debug.print( - "ForEachRange mismatch for left = " # Nat.toText(left) # ", right = " # Nat.toText(right) # ": expected " # debug_show (expected) # ", got " # debug_show (result) - ); - return false - } - } - }; - true -}; - func testIndexOf(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(2 * n, func(i) = i % n)); if (n == 0) { @@ -1383,42 +1287,6 @@ func testSort(n : Nat) : Bool { List.equal(vec, List.fromArray(Array.sort(array, Int.compare)), Int.equal) }; -func testIsSorted(n : Nat) : Bool { - let sorted = List.tabulate(n, func i = i); - if (not List.isSorted(sorted, Nat.compare)) { - Debug.print("isSorted fails on " # List.toText(sorted, Nat.toText)); - return false - }; - - let notSorted = List.tabulate(n, func i = n - i - 1); - if (List.size(notSorted) >= 2 and List.isSorted(notSorted, Nat.compare)) { - Debug.print("isSorted fails on " # List.toText(notSorted, Nat.toText)); - return false - }; - - true -}; - -func testDeduplicate(n : Nat) : Bool { - if (n != 0) return true; - - let lists = [ - List.fromArray([1, 1, 2, 2, 3, 3]), - List.fromArray([1, 2, 3]), - List.fromArray([1, 1, 2, 3]) - ]; - - for (list in lists.vals()) { - List.deduplicate(list, Nat.equal); - if (not List.equal(list, List.fromArray([1, 2, 3]), Nat.equal)) { - Debug.print("Deduplicate failed for " # List.toText(list, Nat.toText)); - return false - } - }; - - true -}; - func testToArray(n : Nat) : Bool { let array = Array.tabulate(n, func(i) = i); let vec = List.fromArray(array); @@ -1533,25 +1401,6 @@ func testFilter(n : Nat) : Bool { true }; -func testRetain(n : Nat) : Bool { - if (n > 10) return true; - - for (mod in Nat.range(1, n + 1)) { - for (rem in Nat.range(0, mod + 1)) { - let f : Nat -> Bool = func x = x % mod == rem; - let vec = List.fromArray(Array.tabulate(n, func(i) = i)); - let expected = List.filter(vec, f); - List.retain(vec, f); - if (not List.equal(vec, expected, Nat.equal)) { - Debug.print("Retain failed for mod " # Nat.toText(mod) # " and rem " # Nat.toText(rem) # ""); - return false - } - } - }; - - true -}; - func testFilterMap(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); @@ -1801,12 +1650,8 @@ func testMax(n : Nat) : Bool { func runAllTests() { runTest("testNew", testNew); runTest("testInit", testInit); - runTest("testFill", testFill); - runTest("testFillWith", testFillWith); - runTest("testTruncate", testTruncate); runTest("testAdd", testAdd); runTest("testAddRepeat", testAddRepeat); - runTest("testAppend", testAppend); runTest("testRemoveLast", testRemoveLast); runTest("testGet", testGet); runTest("testGetOpt", testGetOpt); @@ -1824,18 +1669,14 @@ func runAllTests() { runTest("testContains", testContains); runTest("testReverse", testReverse); runTest("testSort", testSort); - runTest("testIsSorted", testIsSorted); - runTest("testDeduplicate", testDeduplicate); runTest("testToArray", testToArray); runTest("testToVarArray", testToVarArray); runTest("testFromVarArray", testFromVarArray); runTest("testFromArray", testFromArray); runTest("testFromIter", testFromIter); - runTest("testForEachRange", testForEachRange); runTest("testFoldLeft", testFoldLeft); runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); - runTest("testRetain", testRetain); runTest("testFilterMap", testFilterMap); runTest("testPure", testPure); runTest("testReverseForEach", testReverseForEach); @@ -1961,153 +1802,3 @@ Test.suite( ) } ); - -run( - suite( - "concat slices", - [ - test( - "concat with valid slices", - do { - let list1 = List.fromArray([1, 2, 3]); - let list2 = List.fromArray([4, 5, 6]); - let slice1 = (list1, 0, 2); // [1, 2] - let slice2 = (list2, 1, 3); // [5, 6] - let result = List.concatSlices([slice1, slice2]); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [1, 2, 5, 6])) - ), - test( - "concat with empty slices", - do { - let list1 = List.fromArray([1, 2, 3]); - let slice1 = (list1, 1, 1); // [] - let result = List.concatSlices([slice1]); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [] : [Nat])) - ), - test( - "concat with overlapping slices", - do { - let list1 = List.fromArray([1, 2, 3, 4]); - let slice1 = (list1, 0, 2); // [1, 2] - let slice2 = (list1, 1, 4); // [2, 3, 4] - let result = List.concatSlices([slice1, slice2]); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [1, 2, 2, 3, 4])) - ) - ] - ) -); - -run( - suite( - "concat slices (complicated cases)", - [ - test( - "concat with many slices from different lists", - do { - let l1 = List.fromArray([10, 11, 12, 13]); - let l2 = List.fromArray([20, 21]); - let l3 = List.fromArray([30, 31, 32]); - let slices = [ - (l1, 1, 3), // [11, 12] - (l2, 0, 2), // [20, 21] - (l3, 1, 3) // [31, 32] - ]; - let result = List.concatSlices(slices); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [11, 12, 20, 21, 31, 32])) - ), - test( - "concat with all slices empty", - do { - let l1 = List.fromArray([1, 2]); - let l2 = List.fromArray([3, 4]); - let slices = [ - (l1, 0, 0), // [] - (l2, 1, 1) // [] - ]; - let result = List.concatSlices(slices); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [] : [Nat])) - ), - test( - "concat with single element slices", - do { - let l1 = List.fromArray([1, 2, 3]); - let l2 = List.fromArray([4, 5, 6]); - let slices = [ - (l1, 0, 1), // [1] - (l1, 1, 2), // [2] - (l2, 2, 3) // [6] - ]; - let result = List.concatSlices(slices); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [1, 2, 6])) - ), - test( - "concat with slices covering full and partial lists", - do { - let l1 = List.fromArray([1, 2, 3]); - let l2 = List.fromArray([4, 5, 6, 7]); - let slices = [ - (l1, 0, 3), // [1,2,3] - (l2, 1, 3) // [5,6] - ]; - let result = List.concatSlices(slices); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [1, 2, 3, 5, 6])) - ), - test( - "concat with repeated slices from the same list", - do { - let l = List.fromArray([9, 8, 7, 6]); - let slices = [ - (l, 0, 2), // [9,8] - (l, 2, 4), // [7,6] - (l, 1, 3) // [8,7] - ]; - let result = List.concatSlices(slices); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [9, 8, 7, 6, 8, 7])) - ), - test( - "concat with a large number of small slices", - do { - let l = List.fromArray(Array.tabulate(20, func(i) = i)); - let slices = Array.tabulate<(List.List, Nat, Nat)>(20, func(i) = (l, i, i + 1)); - let result = List.concatSlices(slices); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, Array.tabulate(20, func(i) = i))) - ) - ] - ) -); - -run( - suite( - "concat", - [ - test( - "concat two lists", - do { - let list1 = List.fromArray([1, 2, 3]); - let list2 = List.fromArray([4, 5, 6]); - let result = List.concat([list1, list2]); - List.toArray(result) - }, - M.equals(T.array(T.natTestable, [1, 2, 3, 4, 5, 6])) - ) - ] - ) -) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 29852076d..4e4ae909f 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -509,18 +509,13 @@ "public func addRepeat(list : List, initValue : T, count : Nat)", "public func all(list : List, predicate : T -> Bool) : Bool", "public func any(list : List, predicate : T -> Bool) : Bool", - "public func append(list : List, added : List)", "public func clear(list : List)", "public func clone(list : List) : List", "public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order", - "public func concat(lists : [List]) : List", - "public func concatSlices(slices : [(List, fromInclusive : Nat, toExclusive : Nat)]) : List", "public func contains(list : List, equal : (T, T) -> Bool, element : T) : Bool", - "public func deduplicate(list : List, equal : (T, T) -> Bool)", "public func empty() : List", "public func enumerate(list : List) : Iter.Iter<(Nat, T)>", "public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool", - "public func fill(list : List, value : T)", "public func filter(list : List, predicate : T -> Bool) : List", "public func filterMap(list : List, f : T -> ?R) : List", "public func find(list : List, predicate : T -> Bool) : ?T", @@ -533,7 +528,6 @@ "public func foldRight(list : List, base : A, combine : (T, A) -> A) : A", "public func forEach(list : List, f : T -> ())", "public func forEachEntry(list : List, f : (Nat, T) -> ())", - "public func forEachRange(list : List, f : T -> (), fromInclusive : Nat, toExclusive : Nat)", "public func fromArray(array : [T]) : List", "public func fromIter(iter : Iter.Iter) : List", "public func fromPure(pure : PureList.List) : List", @@ -543,7 +537,6 @@ "public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat", "public func insert(list : List, index : Nat, element : T)", "public func isEmpty(list : List) : Bool", - "public func isSorted(list : List, compare : (T, T) -> Order.Order) : Bool", "public func join(lists : Iter.Iter>) : List", "public func keys(list : List) : Iter.Iter", "public func last(list : List) : ?T", @@ -562,7 +555,6 @@ "public func remove(list : List, index : Nat) : T", "public func removeLast(list : List) : ?T", "public func repeat(initValue : T, size : Nat) : List", - "public func retain(list : List, predicate : T -> Bool)", "public func reverse(list : List) : List", "public func reverseEnumerate(list : List) : Iter.Iter<(Nat, T)>", "public func reverseForEach(list : List, f : T -> ())", @@ -579,7 +571,6 @@ "public func toPure(list : List) : PureList.List", "public func toText(list : List, f : T -> Text) : Text", "public func toVarArray(list : List) : [var T]", - "public func truncate(list : List, newSize : Nat)", "public func values(list : List) : Iter.Iter" ] }, From 5d1707be16989755021e60aa7c42837f8ea82b95 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 6 Jul 2025 20:29:14 +0300 Subject: [PATCH 080/123] Format tests --- test/List.test.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/List.test.mo b/test/List.test.mo index a1f8e7bca..1c3dc05fa 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1801,4 +1801,4 @@ Test.suite( } ) } -); +) From 2ee53854597d4e8da6c41f625d4becd0b6123b93 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 6 Jul 2025 20:29:44 +0300 Subject: [PATCH 081/123] Format --- src/List.mo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/List.mo b/src/List.mo index a131d0f0c..d4717d5dd 100644 --- a/src/List.mo +++ b/src/List.mo @@ -184,7 +184,7 @@ module { blocks[i] := oldBlocks[i]; i += 1 }; - list.blocks := blocks; + list.blocks := blocks }; let blocks = list.blocks; @@ -224,7 +224,7 @@ module { }; list.blockIndex := blockIndex; - list.elementIndex := elementIndex; + list.elementIndex := elementIndex }; /// Add to list `count` copies of the initial value. From 24980c8d63b6a4232f65bf284d4bdf753f0b1967 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 7 Jul 2025 17:29:25 +0300 Subject: [PATCH 082/123] Fix memory leak in addRepeat. --- src/List.mo | 26 +++++++++++++++++--------- test/List.test.mo | 3 ++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/List.mo b/src/List.mo index d4717d5dd..ea1be294b 100644 --- a/src/List.mo +++ b/src/List.mo @@ -187,6 +187,15 @@ module { list.blocks := blocks }; + func fill(block : [var ?T], from : Nat, to : Nat, value : ?T) { + if (Option.isNull(value)) return; + var i = from; + while (i < to) { + block[i] := value; + i += 1 + } + }; + let blocks = list.blocks; var blockIndex = list.blockIndex; var elementIndex = list.elementIndex; @@ -195,7 +204,13 @@ module { while (cnt > 0) { let dbSize = dataBlockSize(blockIndex); if (elementIndex == 0 and dbSize <= cnt) { - blocks[blockIndex] := VarArray.repeat(initValue, dbSize); + var block = blocks[blockIndex]; + if (block.size() == 0) { + blocks[blockIndex] := VarArray.repeat(initValue, dbSize) + } else { + if (block.size() != dbSize) Prim.trap INTERNAL_ERROR; + fill(block, 0, dbSize, initValue) + }; cnt -= dbSize; blockIndex += 1 } else { @@ -205,14 +220,7 @@ module { let from = elementIndex; let to = Nat.min(elementIndex + cnt, dbSize); - let block = blocks[blockIndex]; - if (not Option.isNull(initValue)) { - var i = from; - while (i < to) { - block[i] := initValue; - i += 1 - } - }; + fill(blocks[blockIndex], from, to, initValue); elementIndex := to; if (elementIndex == dbSize) { diff --git a/test/List.test.mo b/test/List.test.mo index af0071669..86f47fd0d 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -951,7 +951,8 @@ func testAddRepeat(n : Nat) : Bool { for (i in Nat.range(0, n + 1)) { for (j in Nat.range(0, n + 1)) { - let vec = List.repeat(0, i); + let vec = List.repeat(0, i + n); + for (_ in Nat.range(0, n)) ignore List.removeLast(vec); List.addRepeat(vec, 1, j); if (List.size(vec) != i + j) { Debug.print("Size mismatch: expected " # Nat.toText(i + j) # ", got " # Nat.toText(List.size(vec))); From 2e68be25284af82e247a2d348edb8e221076a5b0 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 9 Jul 2025 17:40:10 +0300 Subject: [PATCH 083/123] Optimize put. --- src/List.mo | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/List.mo b/src/List.mo index ea1be294b..b7fcc8276 100644 --- a/src/List.mo +++ b/src/List.mo @@ -669,7 +669,7 @@ module { }; /// Overwrites the current element at `index` with `element`. - /// Traps if `index` >= size. Indexing is zero-based. + /// Traps if `index` >= size, error message may not be descriptive. Indexing is zero-based. /// /// Example: /// ```motoko include=import @@ -681,19 +681,19 @@ module { /// /// Runtime: `O(1)` public func put(list : List, index : Nat, value : T) { - let (a, b) = do { - let i = Nat32.fromNat(index); - let lz = Nat32.bitcountLeadingZero(i); - let lz2 = lz >> 1; - if (lz & 1 == 0) { - (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) - } else { - (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) - } + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); + let lz2 = lz >> 1; + let (block, element) = if (lz & 1 == 0) { + (list.blocks[Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))], Nat32.toNat(i & (0xFFFF >> lz2))) + } else { + (list.blocks[Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))], Nat32.toNat(i & (0x7FFF >> lz2))) }; - if (a < list.blockIndex or a == list.blockIndex and b < list.elementIndex) { - list.blocks[a][b] := ?value - } else Prim.trap "List index out of bounds in put" + + switch (block[element]) { + case (?_) block[element] := ?value; + case _ Prim.trap "List index out of bounds in put" + } }; /// Sorts the elements in the list according to `compare`. From 1c30316f3d0a2d6b8ea54710fe96c666ce4e918b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 13 Jul 2025 17:22:11 +0300 Subject: [PATCH 084/123] Added assertValid function. --- src/List.mo | 3 +++ test/List.test.mo | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/List.mo b/src/List.mo index b7fcc8276..56e37d30c 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,6 +14,7 @@ import PureList "pure/List"; import Prim "mo:⛔"; +import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -473,6 +474,7 @@ module { func growIndexBlockIfNeeded(list : List) { if (list.blocks.size() == list.blockIndex) { + Debug.print(debug_show ("grow", list.blocks.size(), newIndexBlockLength(Nat32.fromNat(list.blockIndex)), list.blockIndex, size(list))); let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Nat32.fromNat(list.blockIndex))); var i = 0; while (i < list.blockIndex) { @@ -488,6 +490,7 @@ module { // kind of index of the first block in the super block if ((blockIndex << Nat32.bitcountLeadingZero(blockIndex)) << 2 == 0) { let newLength = newIndexBlockLength(blockIndex); + Debug.print(debug_show ("shrink", list.blocks.size(), newLength, blockIndex, size(list))); if (newLength < list.blocks.size()) { let newBlocks = VarArray.repeat<[var ?T]>([var], newLength); var i = 0; diff --git a/test/List.test.mo b/test/List.test.mo index 86f47fd0d..015d53d3d 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -15,6 +15,50 @@ import Int "../src/Int"; import Debug "../src/Debug"; import { Tuple2 } "../src/Tuples"; import PureList "../src/pure/List"; +import VarArray "../src/VarArray"; +import Option "../src/Option"; + +func assertValid(list : List.List, array : [T], equal : (T, T) -> Bool) { + let blocks = list.blocks; + let blockCount = blocks.size(); + + func good(x : Nat) : Bool { + var y = x; + while (y % 2 == 0) y := y / 2; + y == 1 or y == 3 + }; + + assert good(blocks.size()); + + assert blocks[0].size() == 0; + + var index = 0; + var i = 1; + var nullCount = 0; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + assert sz == Nat32.toNat(1 <>> Nat32.bitcountLeadingZero(Nat32.fromNat(i) / 3)); + if (sz == 0) assert index >= List.size(list); + + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) assert equal(array[index], x); + case null assert index >= List.size(list); + }; + index += 1; + j += 1 + }; + + if (VarArray.all(db, Option.isNull)) { + nullCount += 1; + assert j == list.blockIndex or j == list.blockIndex + 1; + }; + i += 1 + }; + assert nullCount <= 1; +}; let { run; test; suite } = Suite; From 01cda8446a55c649e09ba26fc604d25e26761366 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 13 Jul 2025 17:38:22 +0300 Subject: [PATCH 085/123] Format --- test/List.test.mo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/List.test.mo b/test/List.test.mo index 015d53d3d..ff621843e 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -45,7 +45,7 @@ func assertValid(list : List.List, array : [T], equal : (T, T) -> Bool) { while (j < sz) { switch (db[j]) { case (?x) assert equal(array[index], x); - case null assert index >= List.size(list); + case null assert index >= List.size(list) }; index += 1; j += 1 @@ -53,11 +53,11 @@ func assertValid(list : List.List, array : [T], equal : (T, T) -> Bool) { if (VarArray.all(db, Option.isNull)) { nullCount += 1; - assert j == list.blockIndex or j == list.blockIndex + 1; + assert j == list.blockIndex or j == list.blockIndex + 1 }; i += 1 }; - assert nullCount <= 1; + assert nullCount <= 1 }; let { run; test; suite } = Suite; From dd1e69139384e12cb3a8893ac836851e62cf5e8b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 13 Jul 2025 17:40:41 +0300 Subject: [PATCH 086/123] Remove debug output. --- src/List.mo | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/List.mo b/src/List.mo index 56e37d30c..b7fcc8276 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,7 +14,6 @@ import PureList "pure/List"; import Prim "mo:⛔"; -import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -474,7 +473,6 @@ module { func growIndexBlockIfNeeded(list : List) { if (list.blocks.size() == list.blockIndex) { - Debug.print(debug_show ("grow", list.blocks.size(), newIndexBlockLength(Nat32.fromNat(list.blockIndex)), list.blockIndex, size(list))); let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Nat32.fromNat(list.blockIndex))); var i = 0; while (i < list.blockIndex) { @@ -490,7 +488,6 @@ module { // kind of index of the first block in the super block if ((blockIndex << Nat32.bitcountLeadingZero(blockIndex)) << 2 == 0) { let newLength = newIndexBlockLength(blockIndex); - Debug.print(debug_show ("shrink", list.blocks.size(), newLength, blockIndex, size(list))); if (newLength < list.blocks.size()) { let newBlocks = VarArray.repeat<[var ?T]>([var], newLength); var i = 0; From 95a6ec272f6ea69fe806c4ebc930dc453cdd5cbd Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 13 Jul 2025 17:48:50 +0300 Subject: [PATCH 087/123] Fix changelog. --- Changelog.md | 4 ---- src/List.mo | 1 - 2 files changed, 5 deletions(-) diff --git a/Changelog.md b/Changelog.md index 5c7731a38..f781769da 100644 --- a/Changelog.md +++ b/Changelog.md @@ -19,10 +19,6 @@ * Update code examples in doc comments (#224, #282, #303, #315). * Add `findIndex()` function to modules with `find()` (#321). -## 0.5.0 - -* Add `concat` of slices function. - ## 0.4.0 * Add `isReplicated : () -> Bool` to `InternetComputer` (#213). diff --git a/src/List.mo b/src/List.mo index 87716e06c..cda9c8191 100644 --- a/src/List.mo +++ b/src/List.mo @@ -23,7 +23,6 @@ import Order "Order"; import Option "Option"; import VarArray "VarArray"; import Types "Types"; -import Runtime "Runtime"; module { /// `List` provides a mutable list of elements of type `T`. From 2d6c2ac28e80e47f3a5d0558d08654414e7a9c1c Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 14 Jul 2025 17:15:04 +0300 Subject: [PATCH 088/123] Added assertValid into tests. --- src/List.mo | 7 +++--- test/List.test.mo | 62 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/List.mo b/src/List.mo index b7fcc8276..9b572e034 100644 --- a/src/List.mo +++ b/src/List.mo @@ -563,18 +563,19 @@ module { public func removeLast(list : List) : ?T { var elementIndex = list.elementIndex; if (elementIndex == 0) { - shrinkIndexBlockIfNeeded(list); - var blockIndex = list.blockIndex; if (blockIndex == 1) { return null }; + + shrinkIndexBlockIfNeeded(list); + blockIndex -= 1; elementIndex := list.blocks[blockIndex].size(); // Keep one totally empty block when removing if (blockIndex + 2 < list.blocks.size()) { - if (list.blocks[blockIndex + 2].size() == 0) { + if (list.blocks[blockIndex + 2].size() > 0) { list.blocks[blockIndex + 2] := [var] } }; diff --git a/test/List.test.mo b/test/List.test.mo index ff621843e..7b64cee62 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -18,7 +18,7 @@ import PureList "../src/pure/List"; import VarArray "../src/VarArray"; import Option "../src/Option"; -func assertValid(list : List.List, array : [T], equal : (T, T) -> Bool) { +func assertValid(list : List.List) { let blocks = list.blocks; let blockCount = blocks.size(); @@ -38,26 +38,30 @@ func assertValid(list : List.List, array : [T], equal : (T, T) -> Bool) { while (i < blockCount) { let db = blocks[i]; let sz = db.size(); - assert sz == Nat32.toNat(1 <>> Nat32.bitcountLeadingZero(Nat32.fromNat(i) / 3)); + assert i >= list.blockIndex or sz == Nat32.toNat(1 <>> Nat32.bitcountLeadingZero(Nat32.fromNat(i) / 3)); if (sz == 0) assert index >= List.size(list); var j = 0; while (j < sz) { - switch (db[j]) { - case (?x) assert equal(array[index], x); - case null assert index >= List.size(list) - }; + if (index == List.size(list)) assert i == list.blockIndex and j == list.elementIndex; + assert Option.isNull(db[j]) == (index >= List.size(list)); index += 1; j += 1 }; - if (VarArray.all(db, Option.isNull)) { + if (VarArray.any(db, Option.isNull)) { nullCount += 1; - assert j == list.blockIndex or j == list.blockIndex + 1 + assert i == list.blockIndex or i == list.blockIndex + 1 }; i += 1 }; - assert nullCount <= 1 + assert nullCount <= 2; + + let b = list.blockIndex; + let e = list.elementIndex; + List.add(list, 2 ** 64); + assert list.blocks[b][e] == ?(2 ** 64); + assert List.removeLast(list) == ?(2 ** 64) }; let { run; test; suite } = Suite; @@ -946,7 +950,7 @@ while (i < locate_n) { // Helper function to run tests func runTest(name : Text, test : (Nat) -> Bool) { - let testSizes = [0, 1, 10, 100]; + let testSizes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100]; for (n in testSizes.vals()) { if (test(n)) { Debug.print("✅ " # name # " passed for n = " # Nat.toText(n)) @@ -958,12 +962,16 @@ func runTest(name : Text, test : (Nat) -> Bool) { // Test cases func testNew(n : Nat) : Bool { + if (n > 0) return true; + let vec = List.empty(); + assertValid(vec); List.size(vec) == 0 }; func testInit(n : Nat) : Bool { let vec = List.repeat(1, n); + assertValid(vec); List.size(vec) == n and (n == 0 or (List.get(vec, 0) == 1 and List.get(vec, n - 1 : Nat) == 1)) }; @@ -971,7 +979,8 @@ func testAdd(n : Nat) : Bool { if (n == 0) return true; let vec = List.empty(); for (i in Nat.range(0, n)) { - List.add(vec, i) + List.add(vec, i); + assertValid(vec) }; if (List.size(vec) != n) { @@ -981,6 +990,7 @@ func testAdd(n : Nat) : Bool { for (i in Nat.range(0, n)) { let value = List.get(vec, i); + assertValid(vec); if (value != i) { Debug.print("Value mismatch at index " # Nat.toText(i) # ": expected " # Nat.toText(i) # ", got " # Nat.toText(value)); return false @@ -997,7 +1007,9 @@ func testAddRepeat(n : Nat) : Bool { for (j in Nat.range(0, n + 1)) { let vec = List.repeat(0, i + n); for (_ in Nat.range(0, n)) ignore List.removeLast(vec); + assertValid(vec); List.addRepeat(vec, 1, j); + assertValid(vec); if (List.size(vec) != i + j) { Debug.print("Size mismatch: expected " # Nat.toText(i + j) # ", got " # Nat.toText(List.size(vec))); return false @@ -1018,11 +1030,13 @@ func testAddRepeat(n : Nat) : Bool { func testRemoveLast(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); - var i = n; + assertValid(vec); + var i = n; while (i > 0) { i -= 1; let last = List.removeLast(vec); + assertValid(vec); if (last != ?i) { Debug.print("Unexpected value removed: expected ?" # Nat.toText(i) # ", got " # debug_show (last)); return false @@ -1049,6 +1063,7 @@ func testRemoveLast(n : Nat) : Bool { func testGet(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i + 1)); + assertValid(vec); for (i in Nat.range(1, n + 1)) { let value = List.get(vec, i - 1 : Nat); @@ -1106,18 +1121,23 @@ func testPut(n : Nat) : Bool { func testClear(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); List.clear(vec); + assertValid(vec); List.size(vec) == 0 }; func testClone(n : Nat) : Bool { let vec1 = List.fromArray(Array.tabulate(n, func(i) = i)); + assertValid(vec1); let vec2 = List.clone(vec1); + assertValid(vec2); List.equal(vec1, vec2, Nat.equal) }; func testMap(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + assertValid(vec); let mapped = List.map(vec, func(x) = x * 2); + assertValid(mapped); List.equal(mapped, List.fromArray(Array.tabulate(n, func(i) = i * 2)), Nat.equal) }; @@ -1182,24 +1202,29 @@ func testContains(n : Nat) : Bool { }; func testReverse(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + assertValid(vec); List.reverseInPlace(vec); + assertValid(vec); List.equal(vec, List.fromArray(Array.tabulate(n, func(i) = n - 1 - i)), Nat.equal) }; func testSort(n : Nat) : Bool { - let vec = List.fromArray(Array.tabulate(n, func(i) = (i * 123) % 100 - 50)); - List.sort(vec, Int.compare); - List.equal(vec, List.fromArray(Array.sort(Array.tabulate(n, func(i) = (i * 123) % 100 - 50), Int.compare)), Int.equal) + let vec = List.fromArray(Array.tabulate(n, func(i) = Int.abs((i * 123) % 100 - 50))); + List.sort(vec, Nat.compare); + assertValid(vec); + List.equal(vec, List.fromArray(Array.sort(Array.tabulate(n, func(i) = Int.abs((i * 123) % 100 - 50)), Nat.compare)), Nat.equal) }; func testToArray(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); + assertValid(vec); Array.equal(List.toArray(vec), Array.tabulate(n, func(i) = i), Nat.equal) }; func testFromIter(n : Nat) : Bool { let iter = Nat.range(1, n + 1); let vec = List.fromIter(iter); + assertValid(vec); List.equal(vec, List.fromArray(Array.tabulate(n, func(i) = i + 1)), Nat.equal) }; @@ -1217,6 +1242,7 @@ func testFilter(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); let evens = List.filter(vec, func x = x % 2 == 0); + assertValid(evens); let expectedEvens = List.fromArray(Array.tabulate((n + 1) / 2, func(i) = i * 2)); if (not List.equal(evens, expectedEvens, Nat.equal)) { Debug.print("Filter evens failed"); @@ -1224,12 +1250,14 @@ func testFilter(n : Nat) : Bool { }; let none = List.filter(vec, func _ = false); + assertValid(none); if (not List.isEmpty(none)) { Debug.print("Filter none failed"); return false }; let all = List.filter(vec, func _ = true); + assertValid(all); if (not List.equal(all, vec, Nat.equal)) { Debug.print("Filter all failed"); return false @@ -1242,6 +1270,7 @@ func testFilterMap(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); let doubledEvens = List.filterMap(vec, func x = if (x % 2 == 0) ?(x * 2) else null); + assertValid(doubledEvens); let expectedDoubledEvens = List.fromArray(Array.tabulate((n + 1) / 2, func(i) = i * 4)); if (not List.equal(doubledEvens, expectedDoubledEvens, Nat.equal)) { Debug.print("FilterMap doubled evens failed"); @@ -1249,12 +1278,14 @@ func testFilterMap(n : Nat) : Bool { }; let none = List.filterMap(vec, func _ = null); + assertValid(none); if (not List.isEmpty(none)) { Debug.print("FilterMap none failed"); return false }; let all = List.filterMap(vec, func x = ?x); + assertValid(all); if (not List.equal(all, vec, Nat.equal)) { Debug.print("FilterMap all failed"); return false @@ -1268,6 +1299,7 @@ func testPure(n : Nat) : Bool { let vec = List.fromArray(idArray); let pureList = List.toPure(vec); let newVec = List.fromPure(pureList); + assertValid(newVec); if (not PureList.equal(pureList, PureList.fromArray(idArray), Nat.equal)) { Debug.print("PureList conversion failed"); From 7003eddbd557d15a671019b3d008184f8ee0b021 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 14 Jul 2025 18:59:37 +0300 Subject: [PATCH 089/123] Inline prim functions. --- src/List.mo | 60 ++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/List.mo b/src/List.mo index 9b572e034..7c124bc78 100644 --- a/src/List.mo +++ b/src/List.mo @@ -69,7 +69,7 @@ module { private func repeatInternal(initValue : ?T, size : Nat) : List { let (blockIndex, elementIndex) = locate(size); - let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Prim.natToNat32(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); var i = 1; while (i < blockIndex) { @@ -173,7 +173,7 @@ module { private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { let (b, e) = locate(size(list) + count); - let blocksCount = newIndexBlockLength(Nat32.fromNat(if (e == 0) b - 1 else b)); + let blocksCount = newIndexBlockLength(Prim.natToNat32(if (e == 0) b - 1 else b)); let oldBlocksCount = list.blocks.size(); if (oldBlocksCount < blocksCount) { @@ -432,8 +432,8 @@ module { var elementIndex : Nat } ) : Nat { - let d = Nat32.fromNat(list.blockIndex); - let i = Nat32.fromNat(list.elementIndex); + let d = Prim.natToNat32(list.blockIndex); + let i = Prim.natToNat32(list.elementIndex); // We call all data blocks of the same capacity an "epoch". We number the epochs 0,1,2,... // A data block is in epoch e iff the data block has capacity 2 ** e. @@ -442,7 +442,7 @@ module { // epoch of last data block // e = 32 - lz - let lz = Nat32.bitcountLeadingZero(d / 3); + let lz = Prim.clzNat32(d / 3); // capacity of all prior epochs combined // capacity_before_e = 2 * 4 ** (e - 1) - 1 @@ -455,25 +455,25 @@ module { // there can be overflows, but the result is without overflows, so use addWrap and subWrap // we don't erase bits by >>, so to use <>> is ok - Nat32.toNat((d -% (1 <>> lz)) <>> lz +% i) + Prim.nat32ToNat((d -% (1 <>> lz)) <>> lz +% i) }; func dataBlockSize(blockIndex : Nat) : Nat { // formula for the size of given blockIndex // don't call it for blockIndex == 0 - Nat32.toNat(1 <>> Nat32.bitcountLeadingZero(Nat32.fromNat(blockIndex) / 3)) + Prim.nat32ToNat(1 <>> Prim.clzNat32(Prim.natToNat32(blockIndex) / 3)) }; func newIndexBlockLength(blockIndex : Nat32) : Nat { if (blockIndex <= 1) 2 else { - let s = 30 - Nat32.bitcountLeadingZero(blockIndex); - Nat32.toNat(((blockIndex >> s) +% 1) << s) + let s = 30 - Prim.clzNat32(blockIndex); + Prim.nat32ToNat(((blockIndex >> s) +% 1) << s) } }; func growIndexBlockIfNeeded(list : List) { if (list.blocks.size() == list.blockIndex) { - let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Nat32.fromNat(list.blockIndex))); + let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Prim.natToNat32(list.blockIndex))); var i = 0; while (i < list.blockIndex) { newBlocks[i] := list.blocks[i]; @@ -484,9 +484,9 @@ module { }; func shrinkIndexBlockIfNeeded(list : List) { - let blockIndex = Nat32.fromNat(list.blockIndex); + let blockIndex = Prim.natToNat32(list.blockIndex); // kind of index of the first block in the super block - if ((blockIndex << Nat32.bitcountLeadingZero(blockIndex)) << 2 == 0) { + if ((blockIndex << Prim.clzNat32(blockIndex)) << 2 == 0) { let newLength = newIndexBlockLength(blockIndex); if (newLength < list.blocks.size()) { let newBlocks = VarArray.repeat<[var ?T]>([var], newLength); @@ -594,13 +594,13 @@ module { func locate(index : Nat) : (Nat, Nat) { // see comments in tests - let i = Nat32.fromNat(index); - let lz = Nat32.bitcountLeadingZero(i); + let i = Prim.natToNat32 index; + let lz = Prim.clzNat32 i; let lz2 = lz >> 1; if (lz & 1 == 0) { - (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) + (Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Prim.nat32ToNat(i & (0xFFFF >> lz2))) } else { - (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) + (Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Prim.nat32ToNat(i & (0x7FFF >> lz2))) } }; @@ -623,14 +623,14 @@ module { // case (?element) element; // case (null) Prim.trap ""; // }; - let i = Nat32.fromNat(index); - let lz = Nat32.bitcountLeadingZero(i); + let i = Prim.natToNat32 index; + let lz = Prim.clzNat32 i; let lz2 = lz >> 1; switch ( if (lz & 1 == 0) { - list.blocks[Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))][Nat32.toNat(i & (0xFFFF >> lz2))] + list.blocks[Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))][Prim.nat32ToNat(i & (0xFFFF >> lz2))] } else { - list.blocks[Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))][Nat32.toNat(i & (0x7FFF >> lz2))] + list.blocks[Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))][Prim.nat32ToNat(i & (0x7FFF >> lz2))] } ) { case (?result) return result; @@ -655,13 +655,13 @@ module { /// Space: `O(1)` public func getOpt(list : List, index : Nat) : ?T { let (a, b) = do { - let i = Nat32.fromNat(index); - let lz = Nat32.bitcountLeadingZero(i); + let i = Prim.natToNat32 index; + let lz = Prim.clzNat32 i; let lz2 = lz >> 1; if (lz & 1 == 0) { - (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) + (Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Prim.nat32ToNat(i & (0xFFFF >> lz2))) } else { - (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) + (Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Prim.nat32ToNat(i & (0x7FFF >> lz2))) } }; if (a < list.blockIndex or list.elementIndex != 0 and a == list.blockIndex) { @@ -682,13 +682,13 @@ module { /// /// Runtime: `O(1)` public func put(list : List, index : Nat, value : T) { - let i = Nat32.fromNat(index); - let lz = Nat32.bitcountLeadingZero(i); + let i = Prim.natToNat32 index; + let lz = Prim.clzNat32 i; let lz2 = lz >> 1; let (block, element) = if (lz & 1 == 0) { - (list.blocks[Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))], Nat32.toNat(i & (0xFFFF >> lz2))) + (list.blocks[Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))], Prim.nat32ToNat(i & (0xFFFF >> lz2))) } else { - (list.blocks[Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))], Nat32.toNat(i & (0x7FFF >> lz2))) + (list.blocks[Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))], Prim.nat32ToNat(i & (0x7FFF >> lz2))) }; switch (block[element]) { @@ -1331,7 +1331,7 @@ module { public func fromArray(array : [T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Prim.natToNat32(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); func makeBlock(array : [T], p : Nat, len : Nat, fill : Nat) : [var ?T] { @@ -1427,7 +1427,7 @@ module { public func fromVarArray(array : [var T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Prim.natToNat32(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); func makeBlock(array : [var T], p : Nat, len : Nat, fill : Nat) : [var ?T] { From edfdbd3400159b2692edb4360a5682fd66bf87b6 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 14 Jul 2025 20:54:35 +0300 Subject: [PATCH 090/123] Revert "Inline prim functions." This reverts commit 7003eddbd557d15a671019b3d008184f8ee0b021. --- src/List.mo | 60 ++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/List.mo b/src/List.mo index 7c124bc78..9b572e034 100644 --- a/src/List.mo +++ b/src/List.mo @@ -69,7 +69,7 @@ module { private func repeatInternal(initValue : ?T, size : Nat) : List { let (blockIndex, elementIndex) = locate(size); - let blocks = newIndexBlockLength(Prim.natToNat32(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); var i = 1; while (i < blockIndex) { @@ -173,7 +173,7 @@ module { private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { let (b, e) = locate(size(list) + count); - let blocksCount = newIndexBlockLength(Prim.natToNat32(if (e == 0) b - 1 else b)); + let blocksCount = newIndexBlockLength(Nat32.fromNat(if (e == 0) b - 1 else b)); let oldBlocksCount = list.blocks.size(); if (oldBlocksCount < blocksCount) { @@ -432,8 +432,8 @@ module { var elementIndex : Nat } ) : Nat { - let d = Prim.natToNat32(list.blockIndex); - let i = Prim.natToNat32(list.elementIndex); + let d = Nat32.fromNat(list.blockIndex); + let i = Nat32.fromNat(list.elementIndex); // We call all data blocks of the same capacity an "epoch". We number the epochs 0,1,2,... // A data block is in epoch e iff the data block has capacity 2 ** e. @@ -442,7 +442,7 @@ module { // epoch of last data block // e = 32 - lz - let lz = Prim.clzNat32(d / 3); + let lz = Nat32.bitcountLeadingZero(d / 3); // capacity of all prior epochs combined // capacity_before_e = 2 * 4 ** (e - 1) - 1 @@ -455,25 +455,25 @@ module { // there can be overflows, but the result is without overflows, so use addWrap and subWrap // we don't erase bits by >>, so to use <>> is ok - Prim.nat32ToNat((d -% (1 <>> lz)) <>> lz +% i) + Nat32.toNat((d -% (1 <>> lz)) <>> lz +% i) }; func dataBlockSize(blockIndex : Nat) : Nat { // formula for the size of given blockIndex // don't call it for blockIndex == 0 - Prim.nat32ToNat(1 <>> Prim.clzNat32(Prim.natToNat32(blockIndex) / 3)) + Nat32.toNat(1 <>> Nat32.bitcountLeadingZero(Nat32.fromNat(blockIndex) / 3)) }; func newIndexBlockLength(blockIndex : Nat32) : Nat { if (blockIndex <= 1) 2 else { - let s = 30 - Prim.clzNat32(blockIndex); - Prim.nat32ToNat(((blockIndex >> s) +% 1) << s) + let s = 30 - Nat32.bitcountLeadingZero(blockIndex); + Nat32.toNat(((blockIndex >> s) +% 1) << s) } }; func growIndexBlockIfNeeded(list : List) { if (list.blocks.size() == list.blockIndex) { - let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Prim.natToNat32(list.blockIndex))); + let newBlocks = VarArray.repeat<[var ?T]>([var], newIndexBlockLength(Nat32.fromNat(list.blockIndex))); var i = 0; while (i < list.blockIndex) { newBlocks[i] := list.blocks[i]; @@ -484,9 +484,9 @@ module { }; func shrinkIndexBlockIfNeeded(list : List) { - let blockIndex = Prim.natToNat32(list.blockIndex); + let blockIndex = Nat32.fromNat(list.blockIndex); // kind of index of the first block in the super block - if ((blockIndex << Prim.clzNat32(blockIndex)) << 2 == 0) { + if ((blockIndex << Nat32.bitcountLeadingZero(blockIndex)) << 2 == 0) { let newLength = newIndexBlockLength(blockIndex); if (newLength < list.blocks.size()) { let newBlocks = VarArray.repeat<[var ?T]>([var], newLength); @@ -594,13 +594,13 @@ module { func locate(index : Nat) : (Nat, Nat) { // see comments in tests - let i = Prim.natToNat32 index; - let lz = Prim.clzNat32 i; + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); let lz2 = lz >> 1; if (lz & 1 == 0) { - (Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Prim.nat32ToNat(i & (0xFFFF >> lz2))) + (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) } else { - (Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Prim.nat32ToNat(i & (0x7FFF >> lz2))) + (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) } }; @@ -623,14 +623,14 @@ module { // case (?element) element; // case (null) Prim.trap ""; // }; - let i = Prim.natToNat32 index; - let lz = Prim.clzNat32 i; + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); let lz2 = lz >> 1; switch ( if (lz & 1 == 0) { - list.blocks[Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))][Prim.nat32ToNat(i & (0xFFFF >> lz2))] + list.blocks[Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))][Nat32.toNat(i & (0xFFFF >> lz2))] } else { - list.blocks[Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))][Prim.nat32ToNat(i & (0x7FFF >> lz2))] + list.blocks[Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))][Nat32.toNat(i & (0x7FFF >> lz2))] } ) { case (?result) return result; @@ -655,13 +655,13 @@ module { /// Space: `O(1)` public func getOpt(list : List, index : Nat) : ?T { let (a, b) = do { - let i = Prim.natToNat32 index; - let lz = Prim.clzNat32 i; + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); let lz2 = lz >> 1; if (lz & 1 == 0) { - (Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Prim.nat32ToNat(i & (0xFFFF >> lz2))) + (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & (0xFFFF >> lz2))) } else { - (Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Prim.nat32ToNat(i & (0x7FFF >> lz2))) + (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & (0x7FFF >> lz2))) } }; if (a < list.blockIndex or list.elementIndex != 0 and a == list.blockIndex) { @@ -682,13 +682,13 @@ module { /// /// Runtime: `O(1)` public func put(list : List, index : Nat, value : T) { - let i = Prim.natToNat32 index; - let lz = Prim.clzNat32 i; + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); let lz2 = lz >> 1; let (block, element) = if (lz & 1 == 0) { - (list.blocks[Prim.nat32ToNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))], Prim.nat32ToNat(i & (0xFFFF >> lz2))) + (list.blocks[Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2))], Nat32.toNat(i & (0xFFFF >> lz2))) } else { - (list.blocks[Prim.nat32ToNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))], Prim.nat32ToNat(i & (0x7FFF >> lz2))) + (list.blocks[Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2))], Nat32.toNat(i & (0x7FFF >> lz2))) }; switch (block[element]) { @@ -1331,7 +1331,7 @@ module { public func fromArray(array : [T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = newIndexBlockLength(Prim.natToNat32(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); func makeBlock(array : [T], p : Nat, len : Nat, fill : Nat) : [var ?T] { @@ -1427,7 +1427,7 @@ module { public func fromVarArray(array : [var T]) : List { let (blockIndex, elementIndex) = locate(array.size()); - let blocks = newIndexBlockLength(Prim.natToNat32(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); + let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); func makeBlock(array : [var T], p : Nat, len : Nat, fill : Nat) : [var ?T] { From ee375bc8bd3385ecf14cbe49406900c3b858fa23 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 15 Jul 2025 16:53:37 +0300 Subject: [PATCH 091/123] Refactor addRepeatInternal --- src/List.mo | 55 +++++++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/src/List.mo b/src/List.mo index 9b572e034..c57bbe371 100644 --- a/src/List.mo +++ b/src/List.mo @@ -187,47 +187,36 @@ module { list.blocks := blocks }; - func fill(block : [var ?T], from : Nat, to : Nat, value : ?T) { - if (Option.isNull(value)) return; - var i = from; - while (i < to) { - block[i] := value; - i += 1 - } - }; - let blocks = list.blocks; var blockIndex = list.blockIndex; var elementIndex = list.elementIndex; var cnt = count; - while (cnt > 0) { - let dbSize = dataBlockSize(blockIndex); - if (elementIndex == 0 and dbSize <= cnt) { - var block = blocks[blockIndex]; - if (block.size() == 0) { - blocks[blockIndex] := VarArray.repeat(initValue, dbSize) - } else { - if (block.size() != dbSize) Prim.trap INTERNAL_ERROR; - fill(block, 0, dbSize, initValue) - }; - cnt -= dbSize; - blockIndex += 1 - } else { - if (blocks[blockIndex].size() == 0) { - blocks[blockIndex] := VarArray.repeat(null, dbSize) + label L while (cnt > 0) { + if (blocks[blockIndex].size() == 0) { + let dbSize = dataBlockSize(blockIndex); + if (cnt >= dbSize) { + blocks[blockIndex] := VarArray.repeat(initValue, dbSize); + blockIndex += 1; + cnt -= dbSize; + continue L }; - let from = elementIndex; - let to = Nat.min(elementIndex + cnt, dbSize); + blocks[blockIndex] := VarArray.repeat(null, dbSize) + }; - fill(blocks[blockIndex], from, to, initValue); + let block = blocks[blockIndex]; + let dbSize = block.size(); + let to = Nat.min(elementIndex + cnt, dbSize); + cnt -= to - elementIndex; - elementIndex := to; - if (elementIndex == dbSize) { - elementIndex := 0; - blockIndex += 1 - }; - cnt -= to - from + while (elementIndex < to) { + block[elementIndex] := initValue; + elementIndex += 1 + }; + + if (elementIndex == dbSize) { + elementIndex := 0; + blockIndex += 1 } }; From 775dd816bd304b2b23ee1e0a6baf2cba183c1568 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 15 Jul 2025 17:01:15 +0300 Subject: [PATCH 092/123] Rename sliceToArray, remove insert and remove. --- src/List.mo | 134 ++-------------------------------------------- test/List.test.mo | 57 ++------------------ 2 files changed, 6 insertions(+), 185 deletions(-) diff --git a/src/List.mo b/src/List.mo index eababd0e7..c5b6349c1 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1102,134 +1102,6 @@ module { fromVarArray(array) }; - /// Inserts `element` at `index` in the list, shifting existing elements to the right. - /// Traps if `index > size`. Indexing is zero-based. - /// - /// Example: - /// ```motoko include=import - /// let list = List.fromArray([1, 2, 4]); - /// List.insert(list, 2, 3); // inserts 3 at index 2 - /// assert List.toArray(list) == [1, 2, 3, 4]; - /// ``` - /// - /// Runtime: O(size) - /// - /// Space: O(1) - public func insert(list : List, index : Nat, element : T) { - if (index > size(list)) { - Prim.trap "List index out of bounds in insert" - }; - addRepeatInternal(list, null, 1); - - func shift(block : [var ?T], start : Nat, end : Nat, first : ?T) : ?T { - if (start == end) return null; - - var i = end - 1 : Nat; - let last = block[i]; - while (i > start) { - block[i] := block[i - 1]; - i -= 1 - }; - block[start] := first; - last - }; - - let listElement = list.elementIndex; - let listBlock = list.blockIndex; - - let (blockIndex, elementIndex) = locate(index); - let blocks = list.blocks; - - if (listBlock == blockIndex) { - // should be null - ignore shift(blocks[listBlock], elementIndex, listElement, ?element) - } else { - let db = blocks[blockIndex]; - var last = shift(db, elementIndex, db.size(), ?element); - - var i = blockIndex + 1; - - while (i < listBlock) { - let db = blocks[i]; - last := shift(db, 0, db.size(), last); - i += 1 - }; - - if (listBlock < blocks.size()) { - // should be null - ignore shift(blocks[listBlock], 0, listElement, last) - } - } - }; - - /// Removes the element at `index` from the list, shifting existing elements to the left. - /// Traps if `index >= size`. Indexing is zero-based. - /// - /// Example: - /// ```motoko include=import - /// let list = List.fromArray([1, 2, 3, 4]); - /// let removed = List.remove(list, 2); // removes element at index 2 - /// assert removed == 3; - /// assert List.toArray(list) == [1, 2, 4]; - /// ``` - /// - /// Runtime: `O(size)` - /// - /// Space: `O(1)` - public func remove(list : List, index : Nat) : T { - if (index >= size(list)) { - Prim.trap "List index out of bounds in remove" - }; - - func shift(block : [var ?T], start : Nat, end : Nat, last : ?T) : ?T { - if (start == end) return null; - - let first = block[start]; - - var i = start; - let to = end - 1 : Nat; - while (i < to) { - block[i] := block[i + 1]; - i += 1 - }; - block[to] := last; - first - }; - - let listElement = list.elementIndex; - let listBlock = list.blockIndex; - - let (blockIndex, elementIndex) = locate(index); - let blocks = list.blocks; - - let ret = if (listBlock == blockIndex) { - shift(blocks[listBlock], elementIndex, listElement, null) - } else { - var first : ?T = null; - if (listBlock < blocks.size()) { - first := shift(blocks[listBlock], 0, listElement, null) - }; - - var i = listBlock - 1 : Nat; - while (i > blockIndex) { - let db = blocks[i]; - first := shift(db, 0, db.size(), first); - i -= 1 - }; - - let db = blocks[blockIndex]; - shift(db, elementIndex, db.size(), first) - }; - - // should be null - ignore removeLast(list); - - switch (ret) { - case (?x) x; - case (null) Prim.trap INTERNAL_ERROR - } - }; - /// Finds the first index of `element` in `list` using equality of elements defined /// by `equal`. Returns `null` if `element` is not found. /// @@ -2196,17 +2068,17 @@ module { /// ```motoko include=import /// let array = List.fromArray([1, 2, 3, 4, 5]); /// - /// let slice1 = List.sliceToArray(array, 1, 4); + /// let slice1 = List.subArray(array, 1, 4); /// assert slice1 == [2, 3, 4]; /// - /// let slice2 = List.sliceToArray(array, 1, -1); + /// let slice2 = List.subArray(array, 1, -1); /// assert slice2 == [2, 3, 4]; /// ``` /// /// Runtime: O(toExclusive - fromInclusive) /// /// Space: O(toExclusive - fromInclusive) - public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T] { + public func subArray(list : List, fromInclusive : Int, toExclusive : Int) : [T] { let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); let blocks = list.blocks.size(); var blockIndex = 0; diff --git a/test/List.test.mo b/test/List.test.mo index aaeb5e482..6baf37a28 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1260,12 +1260,12 @@ func testRange(n : Nat) : Bool { true }; -func testSliceToArray(n : Nat) : Bool { +func testSubArray(n : Nat) : Bool { if (n > 10) return true; // Skip large ranges for performance let vec = List.fromArray(Array.tabulate(n, func(i) = i)); for (left in Nat.range(0, n)) { for (right in Nat.range(left, n + 1)) { - let slice = List.sliceToArray(vec, left, right); + let slice = List.subArray(vec, left, right); let expected = Array.tabulate(right - left, func(i) = left + i); if (slice != expected) { Debug.print( @@ -1378,55 +1378,6 @@ func testFromArray(n : Nat) : Bool { List.equal(vec, List.fromArray(array), Nat.equal) }; -func testInsert(n : Nat) : Bool { - for (i in Nat.range(0, n + 1)) { - let list = List.tabulate(n, func i = i); - List.insert(list, i, n); - - if (List.size(list) != n + 1) { - Debug.print("Insert failed: expected size " # Nat.toText(n + 1) # ", got " # Nat.toText(List.size(list))); - return false - }; - - for (j in Nat.range(0, n + 1)) { - let expectedValue = if (j < i) j else if (j == i) n else j - 1 : Nat; - let value = List.get(list, j); - if (value != expectedValue) { - Debug.print("Insert failed at index " # Nat.toText(j) # ": expected " # debug_show (expectedValue) # ", got " # debug_show (value)); - return false - } - } - }; - true -}; - -func testRemove(n : Nat) : Bool { - for (i in Nat.range(0, n)) { - let list = List.tabulate(n, func i = i); - let removed = List.remove(list, i); - - if (removed != i) { - Debug.print("Remove failed: expected " # Nat.toText(i) # ", got " # debug_show (removed)); - return false - }; - - if (List.size(list) != (n - 1 : Nat)) { - Debug.print("Remove failed: expected size " # Nat.toText(n - 1) # ", got " # Nat.toText(List.size(list))); - return false - }; - - for (j in Nat.range(0, n - 1)) { - let expectedValue = if (j < i) j else j + 1; - let value = List.get(list, j); - if (value != expectedValue) { - Debug.print("Remove failed at index " # Nat.toText(j) # ": expected " # debug_show (expectedValue) # ", got " # debug_show (value)); - return false - } - } - }; - true -}; - func testFromIter(n : Nat) : Bool { let iter = Nat.range(1, n + 1); let vec = List.fromIter(iter); @@ -1738,7 +1689,7 @@ func runAllTests() { runTest("testMapInPlace", testMapInPlace); runTest("testFlatMap", testFlatMap); runTest("testRange", testRange); - runTest("testSliceToArray", testSliceToArray); + runTest("testSubArray", testSubArray); runTest("testIndexOf", testIndexOf); runTest("testLastIndexOf", testLastIndexOf); runTest("testContains", testContains); @@ -1756,8 +1707,6 @@ func runAllTests() { runTest("testPure", testPure); runTest("testReverseForEach", testReverseForEach); runTest("testForEach", testForEach); - runTest("testInsert", testInsert); - runTest("testRemove", testRemove); runTest("testFlatten", testFlatten); runTest("testJoin", testJoin); runTest("testTabulate", testTabulate); From 4a73347b023d667fc3aaf80c475c44c327da942e Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 15 Jul 2025 17:04:07 +0300 Subject: [PATCH 093/123] fix api. --- validation/api/api.lock.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 4e4ae909f..712a8ee63 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -535,7 +535,6 @@ "public func get(list : List, index : Nat) : T", "public func getOpt(list : List, index : Nat) : ?T", "public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat", - "public func insert(list : List, index : Nat, element : T)", "public func isEmpty(list : List) : Bool", "public func join(lists : Iter.Iter>) : List", "public func keys(list : List) : Iter.Iter", @@ -552,7 +551,6 @@ "public func prevIndexOf(list : List, element : T, fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat", "public func put(list : List, index : Nat, value : T)", "public func range(list : List, fromInclusive : Int, toExclusive : Int) : Iter.Iter", - "public func remove(list : List, index : Nat) : T", "public func removeLast(list : List) : ?T", "public func repeat(initValue : T, size : Nat) : List", "public func reverse(list : List) : List", @@ -563,9 +561,9 @@ "public func reverseValues(list : List) : Iter.Iter", "public func singleton(element : T) : List", "public func size( list : { var blockIndex : Nat; var elementIndex : Nat } ) : Nat", - "public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", "public func sort(list : List, compare : (T, T) -> Order.Order) : List", "public func sortInPlace(list : List, compare : (T, T) -> Order.Order)", + "public func subArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", "public func tabulate(size : Nat, generator : Nat -> T) : List", "public func toArray(list : List) : [T]", "public func toPure(list : List) : PureList.List", From badc1065fd0a4564a0073f7c053d53a629b64fc1 Mon Sep 17 00:00:00 2001 From: Timo Hanke Date: Wed, 16 Jul 2025 21:12:26 +0200 Subject: [PATCH 094/123] Type in fromPure comment --- src/List.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index c57bbe371..0bc432c59 100644 --- a/src/List.mo +++ b/src/List.mo @@ -144,7 +144,7 @@ module { result }; - /// Converts a purely functional `List` to a mutable `List`. + /// Converts a purely functional `PureList` to a mutable `List`. /// /// Example: /// ```motoko include=import From f2e327dec53383ea49abed9d0cf64e707e1b9939 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 17 Jul 2025 16:30:47 +0300 Subject: [PATCH 095/123] Try to optimize tabulate. --- src/List.mo | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/List.mo b/src/List.mo index 565efb4d6..c6dccd8c7 100644 --- a/src/List.mo +++ b/src/List.mo @@ -145,7 +145,7 @@ module { result }; - /// Converts a purely functional `PureList` to a mutable `List`. + /// Converts a purely functional `PureList` to a `List`. /// /// Example: /// ```motoko include=import @@ -287,29 +287,20 @@ module { let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); - func makeBlock(generator : Nat -> T, p : Nat, len : Nat, fill : Nat) : [var ?T] { - let block = VarArray.repeat(null, len); - var j = 0; - var pos = p; - while (j < fill) { - block[j] := ?generator(pos); - j += 1; - pos += 1 - }; - block - }; - var i = 1; var pos = 0; while (i < blockIndex) { let len = dataBlockSize(i); - dataBlocks[i] := makeBlock(generator, pos, len, len); + dataBlocks[i] := VarArray.tabulate(len, func i = ?generator(pos + i)); pos += len; i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - dataBlocks[i] := makeBlock(generator, pos, dataBlockSize(i), elementIndex) + dataBlocks[i] := VarArray.tabulate(dataBlockSize(blockIndex), func i { + let index = pos + i; + if (index < size) ?generator(index) else null; + }); }; { From 83e4a56a764121076ef7e290f3788bdf6f724817 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 17 Jul 2025 16:41:24 +0300 Subject: [PATCH 096/123] Optimize with tabulateVar. --- src/List.mo | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/List.mo b/src/List.mo index c6dccd8c7..0d3d5ae83 100644 --- a/src/List.mo +++ b/src/List.mo @@ -78,15 +78,13 @@ module { i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - let block = VarArray.repeat(null, dataBlockSize(i)); - if (not Option.isNull(initValue)) { - var j = 0; - while (j < elementIndex) { - block[j] := initValue; - j += 1 - } - }; - dataBlocks[i] := block + dataBlocks[blockIndex] := if (Option.isNull(initValue)) VarArray.repeat( + null, + dataBlockSize(blockIndex) + ) else VarArray.tabulate( + dataBlockSize(blockIndex), + func i = if (i < elementIndex) initValue else null + ) }; { @@ -297,10 +295,10 @@ module { i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - dataBlocks[i] := VarArray.tabulate(dataBlockSize(blockIndex), func i { - let index = pos + i; - if (index < size) ?generator(index) else null; - }); + dataBlocks[i] := VarArray.tabulate( + dataBlockSize(blockIndex), + func i = if (i < elementIndex) ?generator(pos + i) else null + ) }; { @@ -1729,29 +1727,20 @@ module { let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); let dataBlocks = VarArray.repeat<[var ?T]>([var], blocks); - func makeBlock(array : [T], p : Nat, len : Nat, fill : Nat) : [var ?T] { - let block = VarArray.repeat(null, len); - var j = 0; - var pos = p; - while (j < fill) { - block[j] := ?array[pos]; - j += 1; - pos += 1 - }; - block - }; - var i = 1; var pos = 0; while (i < blockIndex) { let len = dataBlockSize(i); - dataBlocks[i] := makeBlock(array, pos, len, len); + dataBlocks[i] := VarArray.tabulate(len, func i = ?array[pos + i]); pos += len; i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - dataBlocks[i] := makeBlock(array, pos, dataBlockSize(i), elementIndex) + dataBlocks[i] := VarArray.tabulate( + dataBlockSize(i), + func i = if (i < elementIndex) ?array[pos + i] else null + ) }; { From b9421c8799256bd8b60bd25154359f0a99724ad8 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Fri, 29 Aug 2025 16:12:54 +0300 Subject: [PATCH 097/123] Fix. --- Changelog.md | 33 +++++++++++++++++++++++++++------ src/List.mo | 2 +- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Changelog.md b/Changelog.md index f5bb045e4..ac8841e13 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,11 +1,32 @@ ## Next -<<<<<<< HEAD -* **Breaking:** Enable persistence of `Random` and `AsyncRandom` state in stable memory (#329). -* Added `explode` to `Int16`/`32`/`64`, `Nat16`/`32`/`64`, slicing fixed-length numbers into constituent bytes (#346). -* Fix a bug in `List.last` (#336). +* Clarify difference between `List` and `pure/List` in doc comments (#386). +* Optimize methods in `List` (#337). + +## 1.0.0 + +* Add `sliceToVarArray()` to `Array` and `VarArray` (#377). +* **Breaking:** Standardize function argument order (#376). +* Add example usage documentation to `Types` module (#374). +* Address inconsistent treatment of empty iterators by range functions in `Int` and `Nat` (#369). +* Fix corner cases in `Nat.rangeByInclusive()` (#368). +* **Breaking:** Rename `List.get()` to `List.at()` and `List.getOpt()` to `List.get()` (#367). +* Add `Text.foldLeft()` (#366). +* **Breaking:** Adjust `Int.fromText()` to return `null` instead of `?0` for `"+"` and `"-"` (#365). +* Fix corner case in `sliceToArray()` (#364). +* Add `uniform64()`, `nat64()`, `natRange()`, and `intRange()` to `AsyncRandom` class (#360). +* Make `Nat.toText()` slightly more performant (#358). + +## 0.6.0 + +* Update style guidelines (#353). +* Add `Text.reverse()` (#351). +* Add `fromArray()` and `toArray()` to `Queue` and `pure/Queue` (#349). +* Add `explode()` to `Int16`/`32`/`64`, `Nat16`/`32`/`64`, slicing fixed-length numbers into constituent bytes (#346). * Fix a typo in the `VarArray` documentation (#338). -* Perf: Uses the new Array_tabulateVar primitive to speed up various function in VarArray (#334) +* Fix a bug in `List.last()` (#336). +* Perf: Uses the new `Array_tabulateVar` primitive to speed up various function in `VarArray` (#334). +* **Breaking:** Enable persistence of `Random` and `AsyncRandom` state in stable memory (#329). ## 0.5.0 @@ -61,4 +82,4 @@ ## 0.1.0 -* Initial release +* Initial release \ No newline at end of file diff --git a/src/List.mo b/src/List.mo index c4b3729b7..427bda7c8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -837,7 +837,7 @@ module { /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func find(list : List, predicate : T -> Bool) : ?T { - Option.map(findIndex(list, predicate), func(i) = get(list, i)) + Option.map(findIndex(list, predicate), func(i) = at(list, i)) }; /// Finds the index of the first element in `list` for which `predicate` is true. From e62ccd49d4146bce22c10e9c8596b42351b6d126 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 31 Aug 2025 16:08:12 +0300 Subject: [PATCH 098/123] Fix. --- test/List.test.mo | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/List.test.mo b/test/List.test.mo index b6ac73f17..dc5bf477b 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -871,14 +871,14 @@ func joinWith(xs : List.List, sep : Text) : Text { let size = List.size(xs); if (size == 0) return ""; - if (size == 1) return List.get(xs, 0); + if (size == 1) return List.at(xs, 0); - var result = List.get(xs, 0); + var result = List.at(xs, 0); var i = 0; label l loop { i += 1; if (i >= size) { break l }; - result #= sep # List.get(xs, i) + result #= sep # List.at(xs, i) }; result }; @@ -1158,8 +1158,8 @@ func testAt(n : Nat) : Bool { func testGet(n : Nat) : Bool { let vec = List.tabulate(n, func(i) = i); - for (i in Nat.range(1, n + 1)) { - switch (List.get(vec, i - 1 : Nat)) { + for (i in Nat.range(0, n)) { + switch (List.get(vec, i)) { case (?value) { if (value != i) { Debug.print("get: Mismatch at index " # Nat.toText(i) # ": expected ?" # Nat.toText(i) # ", got ?" # Nat.toText(value)); @@ -1515,7 +1515,7 @@ func testFlatten(n : Nat) : Bool { for (i in Nat.range(0, n)) { for (j in Nat.range(0, i + 1)) { - if (List.get(flattened, (i * (i + 1)) / 2 + j) != j) { + if (List.at(flattened, (i * (i + 1)) / 2 + j) != j) { Debug.print("Flatten value mismatch at index " # Nat.toText((i * (i + 1)) / 2 + j) # ": expected " # Nat.toText(j)); return false } @@ -1540,7 +1540,7 @@ func testJoin(n : Nat) : Bool { for (i in Nat.range(0, n)) { for (j in Nat.range(0, i + 1)) { - if (List.get(flattened, (i * (i + 1)) / 2 + j) != j) { + if (List.at(flattened, (i * (i + 1)) / 2 + j) != j) { Debug.print("Flatten value mismatch at index " # Nat.toText((i * (i + 1)) / 2 + j) # ": expected " # Nat.toText(j)); return false } @@ -1559,8 +1559,8 @@ func testTabulate(n : Nat) : Bool { }; for (i in Nat.range(0, n)) { - if (List.get(tabu, i) != i) { - Debug.print("Tabulate value mismatch at index " # Nat.toText(i) # ": expected " # Nat.toText(i) # ", got " # Nat.toText(List.get(tabu, i))); + if (List.at(tabu, i) != i) { + Debug.print("Tabulate value mismatch at index " # Nat.toText(i) # ": expected " # Nat.toText(i) # ", got " # Nat.toText(List.at(tabu, i))); return false } }; @@ -1571,7 +1571,7 @@ func testTabulate(n : Nat) : Bool { func testNextIndexOf(n : Nat) : Bool { func nextIndexOf(vec : List.List, element : Nat, from : Nat) : ?Nat { for (i in Nat.range(from, List.size(vec))) { - if (List.get(vec, i) == element) { + if (List.at(vec, i) == element) { return ?i } }; @@ -1601,7 +1601,7 @@ func testPrevIndexOf(n : Nat) : Bool { var i = from; while (i > 0) { i -= 1; - if (List.get(vec, i) == element) { + if (List.at(vec, i) == element) { return ?i } }; From f836c0654d1d465ccd1a3dc49c52f3e31135b55b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 14 Sep 2025 19:48:31 +0300 Subject: [PATCH 099/123] Responded to some comments. --- src/List.mo | 66 ++++++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/src/List.mo b/src/List.mo index 0685da6e3..dc3baafc8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -76,9 +76,9 @@ module { dataBlocks[i] := VarArray.repeat(initValue, dataBlockSize(i)); i += 1 }; - if (elementIndex != 0 and blockIndex < blocks) { + if (elementIndex != 0) { let block = VarArray.repeat(null, dataBlockSize(i)); - if (not Option.isNull(initValue)) { + if (Option.isSome(initValue)) { var j = 0; while (j < elementIndex) { block[j] := initValue; @@ -126,7 +126,9 @@ module { let blockIndex = list.blockIndex; let elementIndex = list.elementIndex; - var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + var i = blockIndex; + if (elementIndex == 0) i -= 1; + while (i > 0) { let db = blocks[i]; let sz = db.size(); @@ -572,7 +574,7 @@ module { }; elementIndex -= 1; - var lastDataBlock = list.blocks[list.blockIndex]; + let lastDataBlock = list.blocks[list.blockIndex]; let element = lastDataBlock[elementIndex]; lastDataBlock[elementIndex] := null; @@ -643,6 +645,7 @@ module { /// /// Space: `O(1)` public func get(list : List, index : Nat) : ?T { + // inlined version of locate let (a, b) = do { let i = Nat32.fromNat(index); let lz = Nat32.bitcountLeadingZero(i); @@ -756,6 +759,7 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { + // inlined version of findIndex let blocks = list.blocks; let blockCount = blocks.size(); @@ -798,11 +802,14 @@ module { /// /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { + // inlined version of findLastIndex let blocks = list.blocks; let blockIndex = list.blockIndex; let elementIndex = list.elementIndex; - var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + var i = blockIndex; + if (elementIndex == 0) i -= 1; + while (i > 0) { let db = blocks[i]; let sz = db.size(); @@ -907,7 +914,9 @@ module { let blockIndex = list.blockIndex; let elementIndex = list.elementIndex; - var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + var i = blockIndex; + if (elementIndex == 0) i -= 1; + while (i > 0) { let db = blocks[i]; let sz = db.size(); @@ -1025,28 +1034,7 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `predicate` runs in O(1) time and space. - public func any(list : List, predicate : T -> Bool) : Bool { - let blocks = list.blocks; - let blockCount = blocks.size(); - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return false; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) if (predicate(x)) return true; - case null return false - }; - j += 1 - }; - i += 1 - }; - false - }; + public func any(list : List, predicate : T -> Bool) : Bool = findIndex(list, predicate) != null; /// Returns an Iterator (`Iter`) over the elements of a List. /// Iterator provides a single method `next()`, which returns @@ -1382,7 +1370,7 @@ module { pos += len; i += 1 }; - if (elementIndex != 0 and blockIndex < blocks) { + if (elementIndex != 0) { dataBlocks[i] := makeBlock(array, pos, dataBlockSize(i), elementIndex) }; @@ -1478,7 +1466,7 @@ module { pos += len; i += 1 }; - if (elementIndex != 0 and blockIndex < blocks) { + if (elementIndex != 0) { dataBlocks[i] := makeBlock(array, pos, dataBlockSize(i), elementIndex) }; @@ -1640,7 +1628,9 @@ module { let blockIndex = list.blockIndex; let elementIndex = list.elementIndex; - var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + var i = blockIndex; + if (elementIndex == 0) i -= 1; + while (i > 0) { let db = blocks[i]; let sz = db.size(); @@ -1681,7 +1671,9 @@ module { let blockIndex = list.blockIndex; let elementIndex = list.elementIndex; - var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + var i = blockIndex; + if (elementIndex == 0) i -= 1; + while (i > 0) { let db = blocks[i]; let sz = db.size(); @@ -1750,7 +1742,7 @@ module { let blocks = list.blocks; let blockCount = blocks.size(); - var i = 1; + var i = 2; while (i < blockCount) { let db = blocks[i]; let sz = db.size(); @@ -1802,7 +1794,7 @@ module { let blocks = list.blocks; let blockCount = blocks.size(); - var i = 1; + var i = 2; while (i < blockCount) { let db = blocks[i]; let sz = db.size(); @@ -1960,7 +1952,7 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) text := text # ", " # f(x); + case (?x) text #= ", " # f(x); case null return text }; j += 1 @@ -2040,7 +2032,9 @@ module { let blockIndex = list.blockIndex; let elementIndex = list.elementIndex; - var i = if (blockIndex < blocks.size()) blockIndex else blockIndex - 1 : Nat; + var i = blockIndex; + if (elementIndex == 0) i -= 1; + while (i > 0) { let db = blocks[i]; let sz = db.size(); From 3e8f812e67448c8e92e5dc2782120d634a3117d8 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 20 Sep 2025 18:29:28 +0300 Subject: [PATCH 100/123] Refactor reverse and reverseInPlace. --- src/List.mo | 97 +++++++++++++++++++++++++++++++++-------------- test/List.test.mo | 9 ++++- 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/List.mo b/src/List.mo index dc3baafc8..eae2a91eb 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2034,7 +2034,7 @@ module { var i = blockIndex; if (elementIndex == 0) i -= 1; - + while (i > 0) { let db = blocks[i]; let sz = db.size(); @@ -2068,7 +2068,7 @@ module { /// Runtime: `O(size)` /// /// Space: `O(1)` - public func reverseInPlace(list : List) { + public func reverseInPlace1(list : List) { let vsize = size(list); if (vsize <= 1) return; @@ -2116,6 +2116,50 @@ module { } }; + public func reverseInPlace(list : List) { + let vsize = size(list); + if (vsize <= 1) return; + + let count = vsize / 2; + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var blockIndexBack = list.blockIndex; + var elementIndexBack = list.elementIndex; + var dbBack : [var ?T] = if (blockIndexBack < list.blocks.size()) { + list.blocks[blockIndexBack] + } else { [var] }; + + var i = 1; + var index = 0; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + + var j = 0; + while (j < sz) { + if (index >= count) return; + + if (elementIndexBack == 0) { + blockIndexBack -= 1; + dbBack := list.blocks[blockIndexBack]; + elementIndexBack := dbBack.size() - 1 + } else { + elementIndexBack -= 1 + }; + + let temp = db[j]; + db[j] := dbBack[elementIndexBack]; + dbBack[elementIndexBack] := temp; + + j += 1; + index += 1 + }; + i += 1 + }; + }; + /// Returns a new List with the elements from `list` in reverse order. /// /// Example: @@ -2135,11 +2179,8 @@ module { public func reverse(list : List) : List { let rlist = repeatInternal(null, size(list)); - let blocks = list.blocks.size(); - var blockIndexFront = 0; - var elementIndexFront = 0; - var sz = 0; - var dbFront : [var ?T] = [var]; + let blocks = list.blocks; + let blockCount = blocks.size(); var blockIndexBack = rlist.blockIndex; var elementIndexBack = rlist.elementIndex; @@ -2147,29 +2188,29 @@ module { rlist.blocks[blockIndexBack] } else { [var] }; - loop { - if (elementIndexFront == sz) { - blockIndexFront += 1; - if (blockIndexFront >= blocks) return rlist; - dbFront := list.blocks[blockIndexFront]; - sz := dbFront.size(); - if (sz == 0) return rlist; - elementIndexFront := 0 - }; - - if (elementIndexBack == 0) { - blockIndexBack -= 1; - if (blockIndexBack == 0) return rlist; - dbBack := rlist.blocks[blockIndexBack]; - elementIndexBack := dbBack.size() - 1 - } else { - elementIndexBack -= 1 - }; + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return rlist; - dbBack[elementIndexBack] := dbFront[elementIndexFront]; + var j = 0; + while (j < sz) { + if (elementIndexBack == 0) { + blockIndexBack -= 1; + if (blockIndexBack == 0) return rlist; + dbBack := rlist.blocks[blockIndexBack]; + elementIndexBack := dbBack.size() - 1 + } else { + elementIndexBack -= 1 + }; - elementIndexFront += 1 - } + dbBack[elementIndexBack] := db[j]; + j += 1 + }; + i += 1 + }; + rlist }; /// Returns true if and only if the list is empty. diff --git a/test/List.test.mo b/test/List.test.mo index 3d3fa218b..1c19faa05 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1200,12 +1200,19 @@ func testContains(n : Nat) : Bool { true }; + func testReverse(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); assertValid(vec); + let reversed = List.reverse(vec); + assertValid(reversed); List.reverseInPlace(vec); assertValid(vec); - List.equal(vec, List.fromArray(Array.tabulate(n, func(i) = n - 1 - i)), Nat.equal) + + let inPlaceEqual = List.equal(vec, List.fromArray(Array.tabulate(n, func(i) = n - 1 - i)), Nat.equal); + let reversedEqual = List.equal(reversed, List.fromArray(Array.tabulate(n, func(i) = n - 1 - i)), Nat.equal); + + inPlaceEqual and reversedEqual }; func testSort(n : Nat) : Bool { From 6eea7cba597e3a46662dd04267a9e3397c600d87 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 20 Sep 2025 18:40:58 +0300 Subject: [PATCH 101/123] Fixes. --- src/List.mo | 98 ++++++++++++----------------------------------- test/List.test.mo | 2 +- 2 files changed, 25 insertions(+), 75 deletions(-) diff --git a/src/List.mo b/src/List.mo index eae2a91eb..9f8aa17a8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1893,11 +1893,11 @@ module { let blockCount = Nat.min(blocks1.size(), blocks2.size()); var i = 1; - while (i < blockCount) { + label l while (i < blockCount) { let db1 = blocks1[i]; let db2 = blocks2[i]; let sz = Nat.min(db1.size(), db2.size()); - if (sz == 0) return Nat.compare(size(list1), size(list2)); + if (sz == 0) break l; var j = 0; while (j < sz) { @@ -1907,7 +1907,7 @@ module { case (#greater) return #greater; case _ {} }; - case (_, _) return Nat.compare(size(list1), size(list2)) + case (_, _) break l }; j += 1 }; @@ -1934,34 +1934,32 @@ module { /// /// *Runtime and space assumes that `toText` runs in O(1) time and space. public func toText(list : List, f : T -> Text) : Text { - func toTextInternal(list : List, f : T -> Text) : Text { - var text = switch (first(list)) { - case (?x) f(x); - case null return "" - }; + var text = switch (first(list)) { + case (?x) f(x); + case null return "List[]" + }; - let blocks = list.blocks; - let blockCount = blocks.size(); + let blocks = list.blocks; + let blockCount = blocks.size(); - var i = 2; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return text; + var i = 2; + label l while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) break l; - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) text #= ", " # f(x); - case null return text - }; - j += 1 + var j = 0; + while (j < sz) { + switch (db[j]) { + case (?x) text #= ", " # f(x); + case null break l }; - i += 1 + j += 1 }; - text + i += 1 }; - "List[" # toTextInternal(list, f) # "]" + + "List[" # text # "]" }; /// Collapses the elements in `list` into a single value by starting with `base` @@ -2068,54 +2066,6 @@ module { /// Runtime: `O(size)` /// /// Space: `O(1)` - public func reverseInPlace1(list : List) { - let vsize = size(list); - if (vsize <= 1) return; - - let count = vsize / 2; - var i = 0; - - let blocks = list.blocks.size(); - var blockIndexFront = 0; - var elementIndexFront = 0; - var sz = 0; - var dbFront : [var ?T] = [var]; - - var blockIndexBack = list.blockIndex; - var elementIndexBack = list.elementIndex; - var dbBack : [var ?T] = if (blockIndexBack < list.blocks.size()) { - list.blocks[blockIndexBack] - } else { [var] }; - - while (i < count) { - if (elementIndexFront == sz) { - blockIndexFront += 1; - if (blockIndexFront >= blocks) return; - dbFront := list.blocks[blockIndexFront]; - sz := dbFront.size(); - if (sz == 0) return; - elementIndexFront := 0 - }; - - if (elementIndexBack == 0) { - blockIndexBack -= 1; - if (blockIndexBack == 0) return; - dbBack := list.blocks[blockIndexBack]; - elementIndexBack := dbBack.size() - 1 - } else { - elementIndexBack -= 1 - }; - - let temp = dbFront[elementIndexFront]; - dbFront[elementIndexFront] := dbBack[elementIndexBack]; - dbBack[elementIndexBack] := temp; - - elementIndexFront += 1; - - i += 1 - } - }; - public func reverseInPlace(list : List) { let vsize = size(list); if (vsize <= 1) return; @@ -2157,7 +2107,7 @@ module { index += 1 }; i += 1 - }; + } }; /// Returns a new List with the elements from `list` in reverse order. diff --git a/test/List.test.mo b/test/List.test.mo index 1c19faa05..6b4ff9ecf 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1208,7 +1208,7 @@ func testReverse(n : Nat) : Bool { assertValid(reversed); List.reverseInPlace(vec); assertValid(vec); - + let inPlaceEqual = List.equal(vec, List.fromArray(Array.tabulate(n, func(i) = n - 1 - i)), Nat.equal); let reversedEqual = List.equal(reversed, List.fromArray(Array.tabulate(n, func(i) = n - 1 - i)), Nat.equal); From 8e13bff5de49837ab7a4425ab672539017fb8711 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 22 Sep 2025 16:31:53 +0300 Subject: [PATCH 102/123] Optimize reverseInPlace. --- src/List.mo | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/List.mo b/src/List.mo index 9f8aa17a8..43a0bc044 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2070,10 +2070,9 @@ module { let vsize = size(list); if (vsize <= 1) return; - let count = vsize / 2; + let (finalBlock, finalElement) = locate(vsize / 2); let blocks = list.blocks; - let blockCount = blocks.size(); var blockIndexBack = list.blockIndex; var elementIndexBack = list.elementIndex; @@ -2083,14 +2082,12 @@ module { var i = 1; var index = 0; - while (i < blockCount) { + while (i <= finalBlock) { let db = blocks[i]; - let sz = db.size(); + let sz = if (i == finalBlock) finalElement else db.size(); var j = 0; while (j < sz) { - if (index >= count) return; - if (elementIndexBack == 0) { blockIndexBack -= 1; dbBack := list.blocks[blockIndexBack]; From 8eb4e1e5b669e878a80a8d548dd5ab0fc9bc52be Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 23 Sep 2025 19:21:07 +0300 Subject: [PATCH 103/123] Try to optimize binarySearch. --- src/List.mo | 92 +++++++++++++++++++++++++++++++++++++++++++---- test/List.test.mo | 37 ++++++++++++++++++- 2 files changed, 122 insertions(+), 7 deletions(-) diff --git a/src/List.mo b/src/List.mo index 43a0bc044..5729d9489 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,6 +14,7 @@ import PureList "pure/List"; import Prim "mo:⛔"; +import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -962,17 +963,96 @@ module { #found : Nat; #insertionIndex : Nat } { + /* + 1 * 0 = 0 + + 1 * 1 = +1 + 1 * 1 = 1 + + 1 * 2 = 2 + 2 * 2 = 4 + 3 + + 2 * 4 = 8 + 4 * 4 = 16 + 6 + + 4 * 8 = 32 + 8 * 8 = 64 + 12 + + 8 * 16 = 128 + 16 * 16 = 256 + 24 + + */ + // We call all data blocks of the same capacity an "epoch". We number the epochs 0,1,2,... + // A data block is in epoch e iff the data block has capacity 2 ** e. + // Each epoch starting with epoch 1 spans exactly two super blocks. + // Super block s falls in epoch ceil(s/2). + + // epoch of last data block + let b = if (list.elementIndex == 0) list.blockIndex - 1 : Nat else list.blockIndex; + let epoch = (32 - Nat32.bitcountLeadingZero(Nat32.fromNat(b) / 3)); + if (epoch == 0) { + for (i in Nat.range(0, size(list))) { + let x = at(list, i); + switch (compare(x, element)) { + case (#less) {}; + case (#equal) return #found(i); + case (#greater) return #insertionIndex(i) + } + }; + return #insertionIndex(size(list)) + }; + + var firstInEpoch = Nat32.toNat((1 << epoch) / 2); + while (firstInEpoch != 0 and compare(Option.unwrap(list.blocks[firstInEpoch * 3][0]), element) == #greater) { + firstInEpoch /= 2 + }; + + if (firstInEpoch == 0) { + for (i in Nat.range(0, size(list))) { + let x = at(list, i); + switch (compare(x, element)) { + case (#less) {}; + case (#equal) return #found(i); + case (#greater) return #insertionIndex(i) + } + }; + return #insertionIndex(size(list)) + }; + + firstInEpoch *= 3; + + let block = do { + var left = firstInEpoch; + var right = if (firstInEpoch * 2 < b) firstInEpoch * 2 else b + 1; + while (right - left : Nat > 1) { + let mid = (left + right) / 2; + let midElement = Option.unwrap(list.blocks[mid][0]); + switch (compare(midElement, element)) { + case (#less) left := mid; + case (#equal) return #found(size({ var blockIndex = mid; var elementIndex = 0 })); + case (#greater) right := mid + } + }; + left + }; + var left = 0; - var right = size(list); - while (left < right) { + var right = if (block == list.blockIndex) list.elementIndex else list.blocks[block].size(); + while (left != right) { let mid = (left + right) / 2; - switch (compare(at(list, mid), element)) { + let midElement = Option.unwrap(list.blocks[block][mid]); + switch (compare(midElement, element)) { case (#less) left := mid + 1; - case (#greater) right := mid; - case (#equal) return #found mid + case (#equal) return #found(size({ var blockIndex = block; var elementIndex = mid })); + case (#greater) right := mid } }; - #insertionIndex left + + #insertionIndex(size({ var blockIndex = block; var elementIndex = left })) }; /// Returns true iff every element in `list` satisfies `predicate`. diff --git a/test/List.test.mo b/test/List.test.mo index 6b4ff9ecf..31aa4f89d 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -18,6 +18,8 @@ import PureList "../src/pure/List"; import VarArray "../src/VarArray"; import Option "../src/Option"; +// Runtime.trap(debug_show Nat.compare(4, 2)); + func assertValid(list : List.List) { let blocks = list.blocks; let blockCount = blocks.size(); @@ -1348,6 +1350,38 @@ func testForEach(n : Nat) : Bool { true }; +func testBinarySearch(n : Nat) : Bool { + let vec = List.fromArray(Array.tabulate(n, func(i) = i * 2)); + if (n == 0) { + return List.binarySearch(vec, Nat.compare, 0) == #insertionIndex(0) and List.binarySearch(vec, Nat.compare, 1) == #insertionIndex(0) + }; + for (i in Nat.range(0, n)) { + let value = i * 2; + let index = List.binarySearch(vec, Nat.compare, value); + if (index != #found i) { + Debug.print("binarySearch failed for value = " # Nat.toText(value) # ", expected #found " # Nat.toText(i) # ", got " # debug_show (index)); + Debug.print("vec = " # debug_show (vec)); + return false + }; + let notFoundIndex = List.binarySearch(vec, Nat.compare, value + 1); + if (notFoundIndex != #insertionIndex(i + 1)) { + Debug.print("binarySearch should have returned null for value = " # Nat.toText(value + 1) # ", but got " # debug_show (notFoundIndex)); + return false + } + }; + do { + let vec = List.repeat(0, n); + switch (List.binarySearch(vec, Nat.compare, 0)) { + case (#insertionIndex index) { + Debug.print("binarySearch on all-equal elements failed, expected #found 0, got #insertionIndex " # Nat.toText(index)); + return false + }; + case (_) {} + } + }; + List.binarySearch(vec, Nat.compare, n * 2) == #insertionIndex(n) +}; + // Run all tests func runAllTests() { runTest("testNew", testNew); @@ -1374,7 +1408,8 @@ func runAllTests() { runTest("testFilterMap", testFilterMap); runTest("testPure", testPure); runTest("testReverseForEach", testReverseForEach); - runTest("testForEach", testForEach) + runTest("testForEach", testForEach); + runTest("testBinarySearch", testBinarySearch) }; // Run all tests From 39aa58e10a0b6f43b6cf1b8cb232831350c4a638 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 23 Sep 2025 22:32:25 +0300 Subject: [PATCH 104/123] Refactor binarySearch. --- src/List.mo | 57 +++++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/List.mo b/src/List.mo index 5729d9489..7231f8a52 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,7 +14,6 @@ import PureList "pure/List"; import Prim "mo:⛔"; -import Debug "mo:base/Debug"; import Nat32 "Nat32"; import Array "Array"; import Iter "Iter"; @@ -992,27 +991,18 @@ module { // Super block s falls in epoch ceil(s/2). // epoch of last data block + let blocks = list.blocks; let b = if (list.elementIndex == 0) list.blockIndex - 1 : Nat else list.blockIndex; let epoch = (32 - Nat32.bitcountLeadingZero(Nat32.fromNat(b) / 3)); - if (epoch == 0) { - for (i in Nat.range(0, size(list))) { - let x = at(list, i); - switch (compare(x, element)) { - case (#less) {}; - case (#equal) return #found(i); - case (#greater) return #insertionIndex(i) - } - }; - return #insertionIndex(size(list)) - }; - var firstInEpoch = Nat32.toNat((1 << epoch) / 2); - while (firstInEpoch != 0 and compare(Option.unwrap(list.blocks[firstInEpoch * 3][0]), element) == #greater) { + + while (firstInEpoch != 0 and compare(Option.unwrap(blocks[firstInEpoch * 3][0]), element) == #greater) { firstInEpoch /= 2 }; - + if (firstInEpoch == 0) { - for (i in Nat.range(0, size(list))) { + let to = Nat.min(size(list), 2); + for (i in Nat.range(0, to)) { let x = at(list, i); switch (compare(x, element)) { case (#less) {}; @@ -1020,39 +1010,42 @@ module { case (#greater) return #insertionIndex(i) } }; - return #insertionIndex(size(list)) + return #insertionIndex(to) }; firstInEpoch *= 3; - let block = do { + let blockIndex = do { var left = firstInEpoch; var right = if (firstInEpoch * 2 < b) firstInEpoch * 2 else b + 1; while (right - left : Nat > 1) { let mid = (left + right) / 2; - let midElement = Option.unwrap(list.blocks[mid][0]); + let midElement = Option.unwrap(blocks[mid][0]); switch (compare(midElement, element)) { case (#less) left := mid; - case (#equal) return #found(size({ var blockIndex = mid; var elementIndex = 0 })); - case (#greater) right := mid + case (#greater) right := mid; + case (#equal) return #found(size({ var blockIndex = mid; var elementIndex = 0 })) } }; left }; - var left = 0; - var right = if (block == list.blockIndex) list.elementIndex else list.blocks[block].size(); - while (left != right) { - let mid = (left + right) / 2; - let midElement = Option.unwrap(list.blocks[block][mid]); - switch (compare(midElement, element)) { - case (#less) left := mid + 1; - case (#equal) return #found(size({ var blockIndex = block; var elementIndex = mid })); - case (#greater) right := mid - } + let elementIndex = do { + let block = blocks[blockIndex]; + var left = 0; + var right = if (blockIndex == list.blockIndex) list.elementIndex else block.size(); + while (left != right) { + let mid = (left + right) / 2; + switch (compare(Option.unwrap(block[mid]), element)) { + case (#less) left := mid + 1; + case (#greater) right := mid; + case (#equal) return #found(size({ var blockIndex = blockIndex; var elementIndex = mid })) + } + }; + left }; - #insertionIndex(size({ var blockIndex = block; var elementIndex = left })) + #insertionIndex(size({ var blockIndex = blockIndex; var elementIndex = elementIndex })) }; /// Returns true iff every element in `list` satisfies `predicate`. From e16f0bd6db835497ba913455721cdf6e3c600c4e Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Wed, 24 Sep 2025 15:40:29 +0300 Subject: [PATCH 105/123] Fix. --- src/List.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index 7231f8a52..a099da78e 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1017,7 +1017,7 @@ module { let blockIndex = do { var left = firstInEpoch; - var right = if (firstInEpoch * 2 < b) firstInEpoch * 2 else b + 1; + var right = Nat.min(b + 1, firstInEpoch * 2); while (right - left : Nat > 1) { let mid = (left + right) / 2; let midElement = Option.unwrap(blocks[mid][0]); From 107150fe62a2dea453bf900d1427de88f35e0734 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Thu, 25 Sep 2025 17:53:03 +0300 Subject: [PATCH 106/123] Commented binary search. --- src/List.mo | 66 +++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/src/List.mo b/src/List.mo index a099da78e..10b2b9c94 100644 --- a/src/List.mo +++ b/src/List.mo @@ -962,45 +962,34 @@ module { #found : Nat; #insertionIndex : Nat } { - /* - 1 * 0 = 0 - - 1 * 1 = +1 - 1 * 1 = 1 - - 1 * 2 = 2 - 2 * 2 = 4 - 3 - - 2 * 4 = 8 - 4 * 4 = 16 - 6 - - 4 * 8 = 32 - 8 * 8 = 64 - 12 - - 8 * 16 = 128 - 16 * 16 = 256 - 24 - - */ // We call all data blocks of the same capacity an "epoch". We number the epochs 0,1,2,... // A data block is in epoch e iff the data block has capacity 2 ** e. // Each epoch starting with epoch 1 spans exactly two super blocks. // Super block s falls in epoch ceil(s/2). + // Each epoch except e=0 contains 3 * 2 ** (e - 1) data blocks - // epoch of last data block let blocks = list.blocks; - let b = if (list.elementIndex == 0) list.blockIndex - 1 : Nat else list.blockIndex; - let epoch = (32 - Nat32.bitcountLeadingZero(Nat32.fromNat(b) / 3)); - var firstInEpoch = Nat32.toNat((1 << epoch) / 2); - - while (firstInEpoch != 0 and compare(Option.unwrap(blocks[firstInEpoch * 3][0]), element) == #greater) { - firstInEpoch /= 2 + let b = list.blockIndex - (if (list.elementIndex == 0) 1 else 0) : Nat; + + // block index x such that blocks[x][0] <= element + let lessOrEqual = do { + // epoch of the last data block + let epoch = 32 - Nat32.bitcountLeadingZero(Nat32.fromNat(b) / 3); + // initially block index is the first in the epoch + var lessOrEqual = Nat32.toNat((1 << epoch) / 2); + + // lessOrEqual * 3 is always the first data block in an epoch + // while the first element of the first data block in an epoch is actually grater then element go to the previous epoch + // as the last epoch is half of the array we each iteration of the search divides the interval in four + while (lessOrEqual != 0 and compare(Option.unwrap(blocks[lessOrEqual * 3][0]), element) == #greater) { + lessOrEqual /= 2 + }; + + lessOrEqual * 3 }; - if (firstInEpoch == 0) { + // Linear search in e=0, there are just two elements + if (lessOrEqual == 0) { let to = Nat.min(size(list), 2); for (i in Nat.range(0, to)) { let x = at(list, i); @@ -1012,16 +1001,16 @@ module { }; return #insertionIndex(to) }; - - firstInEpoch *= 3; - + + // binary search the blockIndex in [left, right) let blockIndex = do { - var left = firstInEpoch; - var right = Nat.min(b + 1, firstInEpoch * 2); + // guarateed less or equal to element + var left = lessOrEqual; + // right is either outside of the array or greater than element + var right = Nat.min(b + 1, lessOrEqual * 2); while (right - left : Nat > 1) { let mid = (left + right) / 2; - let midElement = Option.unwrap(blocks[mid][0]); - switch (compare(midElement, element)) { + switch (compare(Option.unwrap(blocks[mid][0]), element)) { case (#less) left := mid; case (#greater) right := mid; case (#equal) return #found(size({ var blockIndex = mid; var elementIndex = 0 })) @@ -1030,6 +1019,7 @@ module { left }; + // binary search the elementIndex let elementIndex = do { let block = blocks[blockIndex]; var left = 0; From ff7d174074d9aa1d798b2bfd39d7f52784c47b41 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 27 Sep 2025 17:27:56 +0300 Subject: [PATCH 107/123] Added indexByBlockElement function --- src/List.mo | 73 +++++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/src/List.mo b/src/List.mo index 10b2b9c94..e20120314 100644 --- a/src/List.mo +++ b/src/List.mo @@ -66,7 +66,7 @@ module { var elementIndex = 0 }; - private func repeatInternal(initValue : ?T, size : Nat) : List { + func repeatInternal(initValue : ?T, size : Nat) : List { let (blockIndex, elementIndex) = locate(size); let blocks = newIndexBlockLength(Nat32.fromNat(if (elementIndex == 0) { blockIndex - 1 } else blockIndex)); @@ -173,7 +173,7 @@ module { } }; - private func addRepeatInternal(list : List, initValue : ?T, count : Nat) { + func addRepeatInternal(list : List, initValue : ?T, count : Nat) { let (b, e) = locate(size(list) + count); let blocksCount = newIndexBlockLength(Nat32.fromNat(if (e == 0) b - 1 else b)); @@ -408,23 +408,8 @@ module { filtered }; - /// Returns the current number of elements in the list. - /// - /// Example: - /// ```motoko include=import - /// let list = List.empty(); - /// assert List.size(list) == 0 - /// ``` - /// - /// Runtime: `O(1)` (with some internal calculations) - public func size( - list : { - var blockIndex : Nat; - var elementIndex : Nat - } - ) : Nat { - let d = Nat32.fromNat(list.blockIndex); - let i = Nat32.fromNat(list.elementIndex); + func indexByBlockElement(block : Nat, element : Nat) : Nat { + let d = Nat32.fromNat(block); // We call all data blocks of the same capacity an "epoch". We number the epochs 0,1,2,... // A data block is in epoch e iff the data block has capacity 2 ** e. @@ -446,7 +431,25 @@ module { // there can be overflows, but the result is without overflows, so use addWrap and subWrap // we don't erase bits by >>, so to use <>> is ok - Nat32.toNat((d -% (1 <>> lz)) <>> lz +% i) + Nat32.toNat((d -% (1 <>> lz)) <>> lz +% Nat32.fromNat(element)) + }; + + /// Returns the current number of elements in the list. + /// + /// Example: + /// ```motoko include=import + /// let list = List.empty(); + /// assert List.size(list) == 0 + /// ``` + /// + /// Runtime: `O(1)` (with some internal calculations) + public func size(list : List) : Nat { + // due to the design of List (blockIndex, elementIndex) pair points + // exactly to the place where size-th element should be added + // so, it's the inlined version of indexByBlockElement + let d = Nat32.fromNat(list.blockIndex); + let lz = Nat32.bitcountLeadingZero(d / 3); + Nat32.toNat((d -% (1 <>> lz)) <>> lz +% Nat32.fromNat(list.elementIndex)) }; func dataBlockSize(blockIndex : Nat) : Nat { @@ -772,10 +775,7 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) if (equal(x, element)) return ?size({ - var blockIndex = i; - var elementIndex = j - }); + case (?x) if (equal(x, element)) return ?indexByBlockElement(i, j); case null return null }; j += 1 @@ -817,10 +817,7 @@ module { while (j > 0) { j -= 1; switch (db[j]) { - case (?x) if (equal(x, element)) return ?size({ - var blockIndex = i; - var elementIndex = j - }); + case (?x) if (equal(x, element)) return ?indexByBlockElement(i, j); case null Prim.trap INTERNAL_ERROR } }; @@ -878,10 +875,7 @@ module { var j = 0; while (j < sz) { switch (db[j]) { - case (?x) if (predicate(x)) return ?size({ - var blockIndex = i; - var elementIndex = j - }); + case (?x) if (predicate(x)) return ?indexByBlockElement(i, j); case null return null }; j += 1 @@ -924,10 +918,7 @@ module { while (j > 0) { j -= 1; switch (db[j]) { - case (?x) if (predicate(x)) return ?size({ - var blockIndex = i; - var elementIndex = j - }); + case (?x) if (predicate(x)) return ?indexByBlockElement(i, j); case null Prim.trap INTERNAL_ERROR } }; @@ -984,7 +975,7 @@ module { while (lessOrEqual != 0 and compare(Option.unwrap(blocks[lessOrEqual * 3][0]), element) == #greater) { lessOrEqual /= 2 }; - + lessOrEqual * 3 }; @@ -1001,7 +992,7 @@ module { }; return #insertionIndex(to) }; - + // binary search the blockIndex in [left, right) let blockIndex = do { // guarateed less or equal to element @@ -1013,7 +1004,7 @@ module { switch (compare(Option.unwrap(blocks[mid][0]), element)) { case (#less) left := mid; case (#greater) right := mid; - case (#equal) return #found(size({ var blockIndex = mid; var elementIndex = 0 })) + case (#equal) return #found(indexByBlockElement(mid, 0)) } }; left @@ -1029,13 +1020,13 @@ module { switch (compare(Option.unwrap(block[mid]), element)) { case (#less) left := mid + 1; case (#greater) right := mid; - case (#equal) return #found(size({ var blockIndex = blockIndex; var elementIndex = mid })) + case (#equal) return #found(indexByBlockElement(blockIndex, mid)) } }; left }; - #insertionIndex(size({ var blockIndex = blockIndex; var elementIndex = elementIndex })) + #insertionIndex(indexByBlockElement(blockIndex, elementIndex)) }; /// Returns true iff every element in `list` satisfies `predicate`. From 626111226009f2aeddf8dcf881432333104c0408 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 27 Sep 2025 18:23:14 +0300 Subject: [PATCH 108/123] Fixes. --- src/List.mo | 9 +++------ validation/api/api.lock.json | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/List.mo b/src/List.mo index e20120314..497e88712 100644 --- a/src/List.mo +++ b/src/List.mo @@ -568,11 +568,8 @@ module { elementIndex := list.blocks[blockIndex].size(); // Keep one totally empty block when removing - if (blockIndex + 2 < list.blocks.size()) { - if (list.blocks[blockIndex + 2].size() > 0) { - list.blocks[blockIndex + 2] := [var] - } - }; + if (blockIndex + 2 < list.blocks.size()) list.blocks[blockIndex + 2] := [var]; + list.blockIndex := blockIndex }; elementIndex -= 1; @@ -1990,7 +1987,7 @@ module { public func toText(list : List, f : T -> Text) : Text { var text = switch (first(list)) { case (?x) f(x); - case null return "List[]" + case null "" }; let blocks = list.blocks; diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 10a51a70c..580a75801 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -557,7 +557,7 @@ "public func reverseInPlace(list : List)", "public func reverseValues(list : List) : Iter.Iter", "public func singleton(element : T) : List", - "public func size( list : { var blockIndex : Nat; var elementIndex : Nat } ) : Nat", + "public func size(list : List) : Nat", "public func sort(list : List, compare : (T, T) -> Order.Order)", "public func toArray(list : List) : [T]", "public func toPure(list : List) : PureList.List", From 574beecdb0a30fa991bd57a490dbf59b90d958b2 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 28 Sep 2025 17:08:22 +0300 Subject: [PATCH 109/123] Comments on List structure. --- src/List.mo | 10 ++- test/List.test.mo | 182 ++++++++++++++++++++++++++-------------------- 2 files changed, 111 insertions(+), 81 deletions(-) diff --git a/src/List.mo b/src/List.mo index 497e88712..ccf3b4853 100644 --- a/src/List.mo +++ b/src/List.mo @@ -42,7 +42,10 @@ module { /// let list = List.empty(); // Creates a new List /// ``` public func empty() : List = { + // the first block is always empty and is present in each List + // this is done to optimize locate, at, get, etc var blocks = [var [var]]; + // can't be 0 in any List var blockIndex = 1; var elementIndex = 0 }; @@ -292,7 +295,12 @@ module { /// /// Runtime: `O(size)` public func map(list : List, f : T -> R) : List { - let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); + let blocks = VarArray.repeat<[var ?R]>( + [var], + newIndexBlockLength( + Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex) + ) + ); let blocksCount = list.blocks.size(); var i = 1; diff --git a/test/List.test.mo b/test/List.test.mo index 31aa4f89d..5a90a7b30 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -18,7 +18,108 @@ import PureList "../src/pure/List"; import VarArray "../src/VarArray"; import Option "../src/Option"; -// Runtime.trap(debug_show Nat.compare(4, 2)); +// IMPLEMENTATION DETAILS BEGIN + +// The structure of list is as follows: +// number of block - size +// 0 - 0 +// 1 - 1 +// 2 - 1 +// 3 - 2 +// ... +// 5 - 2 +// 6 - 4 +// ... +// 11 - 4 +// 12 - 8 +// ... +// 23 - 8 +// 24 - 16 +// ... +// 47 - 16 +// .. +// 3 * 2 ** i - 2 ** (i + 1) +// 3 * 2 ** (i + 1) - 2 ** (i + 1) +// ... + +func locate_readable(index : Nat) : (Nat, Nat) { + // index is any Nat32 except for + // blocks before super block s == 2 ** s + let i = Nat32.fromNat(index); + // element with index 0 located in data block with index 1 + if (i == 0) { + return (1, 0) + }; + let lz = Nat32.bitcountLeadingZero(i); + // super block s = bit length - 1 = (32 - leading zeros) - 1 + // i in binary = zeroes; 1; bits blocks mask; bits element mask + // bit lengths = lz; 1; floor(s / 2); ceil(s / 2) + let s = 31 - lz; + // floor(s / 2) + let down = s >> 1; + // ceil(s / 2) = floor((s + 1) / 2) + let up = (s + 1) >> 1; + // element mask = ceil(s / 2) ones in binary + let e_mask = 1 << up - 1; + //block mask = floor(s / 2) ones in binary + let b_mask = 1 << down - 1; + // data blocks in even super blocks before current = 2 ** ceil(s / 2) + // data blocks in odd super blocks before current = 2 ** floor(s / 2) + // data blocks before the super block = element mask + block mask + // elements before the super block = 2 ** s + // first floor(s / 2) bits in index after the highest bit = index of data block in super block + // the next ceil(s / 2) to the end of binary representation of index + 1 = index of element in data block + (Nat32.toNat(e_mask + b_mask + 2 + (i >> up) & b_mask), Nat32.toNat(i & e_mask)) +}; + +// this was optimized in terms of instructions +func locate_optimal(index : Nat) : (Nat, Nat) { + // super block s = bit length - 1 = (32 - leading zeros) - 1 + // blocks before super block s == 2 ** s + let i = Nat32.fromNat(index); + let lz = Nat32.bitcountLeadingZero(i); + let lz2 = lz >> 1; + // we split into cases to apply different optimizations in each one + if (lz & 1 == 0) { + // ceil(s / 2) = 16 - lz2 + // floor(s / 2) = 15 - lz2 + // i in binary = zeroes; 1; bits blocks mask; bits element mask + // bit lengths = lz; 1; 15 - lz2; 16 - lz2 + // blocks before = 2 ** ceil(s / 2) + 2 ** floor(s / 2) + + // so in order to calculate index of the data block + // we need to shift i by 16 - lz2 and set bit with number 16 - lz2, bit 15 - lz2 is already set + + // element mask = 2 ** (16 - lz2) = (1 << 16) >> lz2 = 0xFFFF >> lz2 + let mask = 0xFFFF >> lz2; + (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & mask)) + } else { + // s / 2 = ceil(s / 2) = floor(s / 2) = 15 - lz2 + // i in binary = zeroes; 1; bits blocks mask; bits element mask + // bit lengths = lz; 1; 15 - lz2; 15 - lz2 + // block mask = element mask = mask = 2 ** (s / 2) - 1 = 2 ** (15 - lz2) - 1 = (1 << 15) >> lz2 = 0x7FFF >> lz2 + // blocks before = 2 * 2 ** (s / 2) + + // so in order to calculate index of the data block + // we need to shift i by 15 - lz2, set bit with number 16 - lz2 and unset bit 15 - lz2 + + let mask = 0x7FFF >> lz2; + (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & mask)) + } +}; + +let locate_n = 1_000; +var i = 0; +while (i < locate_n) { + assert (locate_readable(i) == locate_optimal(i)); + assert (locate_readable(1_000_000 + i) == locate_optimal(1_000_000 + i)); + assert (locate_readable(1_000_000_000 + i) == locate_optimal(1_000_000_000 + i)); + assert (locate_readable(2_000_000_000 + i) == locate_optimal(2_000_000_000 + i)); + assert (locate_readable(2 ** 32 - 1 - i : Nat) == locate_optimal(2 ** 32 - 1 - i : Nat)); + i += 1 +}; + +// IMPLEMENTATION DETAILS END func assertValid(list : List.List) { let blocks = list.blocks; @@ -869,85 +970,6 @@ run( ) ); -/* --------------------------------------- */ - -func locate_readable(index : Nat) : (Nat, Nat) { - // index is any Nat32 except for - // blocks before super block s == 2 ** s - let i = Nat32.fromNat(index); - // element with index 0 located in data block with index 1 - if (i == 0) { - return (1, 0) - }; - let lz = Nat32.bitcountLeadingZero(i); - // super block s = bit length - 1 = (32 - leading zeros) - 1 - // i in binary = zeroes; 1; bits blocks mask; bits element mask - // bit lengths = lz; 1; floor(s / 2); ceil(s / 2) - let s = 31 - lz; - // floor(s / 2) - let down = s >> 1; - // ceil(s / 2) = floor((s + 1) / 2) - let up = (s + 1) >> 1; - // element mask = ceil(s / 2) ones in binary - let e_mask = 1 << up - 1; - //block mask = floor(s / 2) ones in binary - let b_mask = 1 << down - 1; - // data blocks in even super blocks before current = 2 ** ceil(s / 2) - // data blocks in odd super blocks before current = 2 ** floor(s / 2) - // data blocks before the super block = element mask + block mask - // elements before the super block = 2 ** s - // first floor(s / 2) bits in index after the highest bit = index of data block in super block - // the next ceil(s / 2) to the end of binary representation of index + 1 = index of element in data block - (Nat32.toNat(e_mask + b_mask + 2 + (i >> up) & b_mask), Nat32.toNat(i & e_mask)) -}; - -// this was optimized in terms of instructions -func locate_optimal(index : Nat) : (Nat, Nat) { - // super block s = bit length - 1 = (32 - leading zeros) - 1 - // blocks before super block s == 2 ** s - let i = Nat32.fromNat(index); - let lz = Nat32.bitcountLeadingZero(i); - let lz2 = lz >> 1; - // we split into cases to apply different optimizations in each one - if (lz & 1 == 0) { - // ceil(s / 2) = 16 - lz2 - // floor(s / 2) = 15 - lz2 - // i in binary = zeroes; 1; bits blocks mask; bits element mask - // bit lengths = lz; 1; 15 - lz2; 16 - lz2 - // blocks before = 2 ** ceil(s / 2) + 2 ** floor(s / 2) - - // so in order to calculate index of the data block - // we need to shift i by 16 - lz2 and set bit with number 16 - lz2, bit 15 - lz2 is already set - - // element mask = 2 ** (16 - lz2) = (1 << 16) >> lz2 = 0xFFFF >> lz2 - let mask = 0xFFFF >> lz2; - (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & mask)) - } else { - // s / 2 = ceil(s / 2) = floor(s / 2) = 15 - lz2 - // i in binary = zeroes; 1; bits blocks mask; bits element mask - // bit lengths = lz; 1; 15 - lz2; 15 - lz2 - // block mask = element mask = mask = 2 ** (s / 2) - 1 = 2 ** (15 - lz2) - 1 = (1 << 15) >> lz2 = 0x7FFF >> lz2 - // blocks before = 2 * 2 ** (s / 2) - - // so in order to calculate index of the data block - // we need to shift i by 15 - lz2, set bit with number 16 - lz2 and unset bit 15 - lz2 - - let mask = 0x7FFF >> lz2; - (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & mask)) - } -}; - -let locate_n = 1_000; -var i = 0; -while (i < locate_n) { - assert (locate_readable(i) == locate_optimal(i)); - assert (locate_readable(1_000_000 + i) == locate_optimal(1_000_000 + i)); - assert (locate_readable(1_000_000_000 + i) == locate_optimal(1_000_000_000 + i)); - assert (locate_readable(2_000_000_000 + i) == locate_optimal(2_000_000_000 + i)); - assert (locate_readable(2 ** 32 - 1 - i : Nat) == locate_optimal(2 ** 32 - 1 - i : Nat)); - i += 1 -}; - // Claude tests (from original Mops package) // Helper function to run tests From 53225c257f08b425dfac708317eb4ef6528255b9 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 29 Sep 2025 15:29:23 +0300 Subject: [PATCH 110/123] Fixes. --- src/List.mo | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/List.mo b/src/List.mo index ccf3b4853..fd59d0279 100644 --- a/src/List.mo +++ b/src/List.mo @@ -274,8 +274,10 @@ module { /// Runtime: `O(size)` public func clone(list : List) : List = { var blocks = VarArray.tabulate<[var ?T]>( - list.blocks.size(), - func(i) = VarArray.clone(list.blocks[i]) + newIndexBlockLength( + Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex) + ), + func(i) = if (i < list.blocks.size()) VarArray.clone(list.blocks[i]) else [var] ); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex @@ -295,16 +297,12 @@ module { /// /// Runtime: `O(size)` public func map(list : List, f : T -> R) : List { - let blocks = VarArray.repeat<[var ?R]>( - [var], - newIndexBlockLength( - Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex) - ) - ); - let blocksCount = list.blocks.size(); + let realBlocks = if (list.elementIndex == 0) list.blockIndex - 1 : Nat else list.blockIndex; + let blocksCount = newIndexBlockLength(Nat32.fromNat(realBlocks)); + let blocks = VarArray.repeat<[var ?R]>([var], blocksCount); var i = 1; - while (i < blocksCount) { + label l while (i <= realBlocks) { let oldBlock = list.blocks[i]; let blockSize = oldBlock.size(); let newBlock = VarArray.repeat(null, blockSize); @@ -314,11 +312,7 @@ module { while (j < blockSize) { switch (oldBlock[j]) { case (?item) newBlock[j] := ?f(item); - case null return { - var blocks = blocks; - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex - } + case null break l }; j += 1 }; From d4c26b6064f5c0b76b875997a16759ab6fcbe3e2 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 29 Sep 2025 15:41:22 +0300 Subject: [PATCH 111/123] Add tests to clone. --- test/List.test.mo | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/List.test.mo b/test/List.test.mo index 5a90a7b30..d1553da39 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1150,6 +1150,12 @@ func testClear(n : Nat) : Bool { }; func testClone(n : Nat) : Bool { + do { + let vec1 = List.empty(); + let vec2 = List.clone(vec1); + assertValid(vec2); + if (not List.equal(vec1, vec2, Nat.equal)) return false + }; let vec1 = List.fromArray(Array.tabulate(n, func(i) = i)); assertValid(vec1); let vec2 = List.clone(vec1); From bf8e2c5d0a5dd8ba251a80d012c6288e6cf513f6 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 29 Sep 2025 17:38:48 +0300 Subject: [PATCH 112/123] Fix. --- src/List.mo | 15 +++++++++------ test/List.test.mo | 7 ++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/List.mo b/src/List.mo index fd59d0279..ab8a7291f 100644 --- a/src/List.mo +++ b/src/List.mo @@ -274,10 +274,11 @@ module { /// Runtime: `O(size)` public func clone(list : List) : List = { var blocks = VarArray.tabulate<[var ?T]>( - newIndexBlockLength( - Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex) + Nat.min( + newIndexBlockLength(Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex)), + list.blocks.size() ), - func(i) = if (i < list.blocks.size()) VarArray.clone(list.blocks[i]) else [var] + func(i) = VarArray.clone(list.blocks[i]) ); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex @@ -297,12 +298,14 @@ module { /// /// Runtime: `O(size)` public func map(list : List, f : T -> R) : List { - let realBlocks = if (list.elementIndex == 0) list.blockIndex - 1 : Nat else list.blockIndex; - let blocksCount = newIndexBlockLength(Nat32.fromNat(realBlocks)); + let blocksCount = Nat.min( + newIndexBlockLength(Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex)), + list.blocks.size() + ); let blocks = VarArray.repeat<[var ?R]>([var], blocksCount); var i = 1; - label l while (i <= realBlocks) { + label l while (i < blocksCount) { let oldBlock = list.blocks[i]; let blockSize = oldBlock.size(); let newBlock = VarArray.repeat(null, blockSize); diff --git a/test/List.test.mo b/test/List.test.mo index d1553da39..e84e29e18 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1150,7 +1150,7 @@ func testClear(n : Nat) : Bool { }; func testClone(n : Nat) : Bool { - do { + if (n == 0) { let vec1 = List.empty(); let vec2 = List.clone(vec1); assertValid(vec2); @@ -1164,6 +1164,11 @@ func testClone(n : Nat) : Bool { }; func testMap(n : Nat) : Bool { + if (n == 0) { + let vec = List.map(List.empty(), func x = x * 2); + assertValid(vec); + if (not List.equal(List.empty(), vec, Nat.equal)) return false + }; let vec = List.fromArray(Array.tabulate(n, func(i) = i)); assertValid(vec); let mapped = List.map(vec, func(x) = x * 2); From f3372ac424a0ab18fff3d4d6ffa1a2e7b1a5b7b6 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 4 Oct 2025 17:27:42 +0300 Subject: [PATCH 113/123] Fix merge errors. --- src/List.mo | 186 +--------------------------------------------- test/List.test.mo | 79 -------------------- 2 files changed, 3 insertions(+), 262 deletions(-) diff --git a/src/List.mo b/src/List.mo index 918a36fd1..ed08944a5 100644 --- a/src/List.mo +++ b/src/List.mo @@ -608,171 +608,6 @@ module { } }; - /// Applies `f` to each element of `list` in place, - /// retaining the original ordering of elements. - /// This modifies the original list. - /// - /// ```motoko include=import - /// import Nat "mo:core/Nat"; - /// - /// let list = List.fromArray([0, 1, 2, 3]); - /// List.mapInPlace(list, func x = x * 3); - /// assert List.equal(list, List.fromArray([0, 3, 6, 9]), Nat.equal); - /// ``` - /// - /// Runtime: O(size) - /// - /// Space: O(size) - /// - /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func mapInPlace(list : List, f : T -> T) { - let blocks = list.blocks; - let blockCount = blocks.size(); - - var i = 1; - while (i < blockCount) { - let db = blocks[i]; - let sz = db.size(); - if (sz == 0) return; - - var j = 0; - while (j < sz) { - switch (db[j]) { - case (?x) db[j] := ?f(x); - case null return - }; - j += 1 - }; - i += 1 - } - }; - - /// Creates a new list by applying `f` to each element in `list` and its index. - /// Retains original ordering of elements. - /// - /// ```motoko include=import - /// import Nat "mo:core/Nat"; - /// - /// let list = List.fromArray([10, 10, 10, 10]); - /// let newList = List.mapEntries(list, func (x, i) = i * x); - /// assert List.equal(newList, List.fromArray([0, 10, 20, 30]), Nat.equal); - /// ``` - /// - /// Runtime: O(size) - /// - /// Space: O(size) - /// - /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func mapEntries(list : List, f : (T, Nat) -> R) : List { - let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); - let blocksCount = list.blocks.size(); - - var index = 0; - - var i = 1; - while (i < blocksCount) { - let oldBlock = list.blocks[i]; - let blockSize = oldBlock.size(); - let newBlock = VarArray.repeat(null, blockSize); - blocks[i] := newBlock; - var j = 0; - - while (j < blockSize) { - switch (oldBlock[j]) { - case (?item) newBlock[j] := ?f(item, index); - case null return { - var blocks = blocks; - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex - } - }; - j += 1; - index += 1 - }; - i += 1 - }; - - { - var blocks = blocks; - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex - } - }; - - /// Creates a new list by applying `f` to each element in `list`. - /// If any invocation of `f` produces an `#err`, returns an `#err`. Otherwise - /// returns an `#ok` containing the new list. - /// - /// ```motoko include=import - /// import Result "mo:core/Result"; - /// - /// let list = List.fromArray([4, 3, 2, 1, 0]); - /// // divide 100 by every element in the list - /// let result = List.mapResult(list, func x { - /// if (x > 0) { - /// #ok(100 / x) - /// } else { - /// #err "Cannot divide by zero" - /// } - /// }); - /// assert Result.isErr(result); - /// ``` - /// - /// Runtime: O(size) - /// - /// Space: O(size) - /// - /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E> { - var error : ?E = null; - - let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); - let blocksCount = list.blocks.size(); - - var i = 1; - while (i < blocksCount) { - let oldBlock = list.blocks[i]; - let blockSize = oldBlock.size(); - let newBlock = VarArray.repeat(null, blockSize); - blocks[i] := newBlock; - var j = 0; - - while (j < blockSize) { - switch (oldBlock[j]) { - case (?item) newBlock[j] := switch (f(item)) { - case (#ok x) ?x; - case (#err e) switch (error) { - case (null) { - error := ?e; - null - }; - case (?_) null - } - }; - case null return switch (error) { - case (null) return #ok { - var blocks = blocks; - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex - }; - case (?e) return #err e - } - }; - j += 1 - }; - i += 1 - }; - - switch (error) { - case (null) return #ok { - var blocks = blocks; - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex - }; - case (?e) return #err e - } - }; - /// Returns a new list containing only the elements from `list` for which the predicate returns true. /// /// Example: @@ -897,23 +732,8 @@ module { result }; - /// Returns the current number of elements in the list. - /// - /// Example: - /// ```motoko include=import - /// let list = List.empty(); - /// assert List.size(list) == 0 - /// ``` - /// - /// Runtime: `O(1)` (with some internal calculations) - public func size( - list : { - var blockIndex : Nat; - var elementIndex : Nat - } - ) : Nat { - let d = Nat32.fromNat(list.blockIndex); - let i = Nat32.fromNat(list.elementIndex); + func indexByBlockElement(blockIndex : Nat, elementIndex : Nat) : Nat { + let d = Nat32.fromNat(blockIndex); // We call all data blocks of the same capacity an "epoch". We number the epochs 0,1,2,... // A data block is in epoch e iff the data block has capacity 2 ** e. @@ -935,7 +755,7 @@ module { // there can be overflows, but the result is without overflows, so use addWrap and subWrap // we don't erase bits by >>, so to use <>> is ok - Nat32.toNat((d -% (1 <>> lz)) <>> lz +% Nat32.fromNat(element)) + Nat32.toNat((d -% (1 <>> lz)) <>> lz +% Nat32.fromNat(elementIndex)) }; /// Returns the current number of elements in the list. diff --git a/test/List.test.mo b/test/List.test.mo index ff2e1ceb5..8eaf98614 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1039,85 +1039,6 @@ run( ) ); -/* --------------------------------------- */ - -func locate_readable(index : Nat) : (Nat, Nat) { - // index is any Nat32 except for - // blocks before super block s == 2 ** s - let i = Nat32.fromNat(index); - // element with index 0 located in data block with index 1 - if (i == 0) { - return (1, 0) - }; - let lz = Nat32.bitcountLeadingZero(i); - // super block s = bit length - 1 = (32 - leading zeros) - 1 - // i in binary = zeroes; 1; bits blocks mask; bits element mask - // bit lengths = lz; 1; floor(s / 2); ceil(s / 2) - let s = 31 - lz; - // floor(s / 2) - let down = s >> 1; - // ceil(s / 2) = floor((s + 1) / 2) - let up = (s + 1) >> 1; - // element mask = ceil(s / 2) ones in binary - let e_mask = 1 << up - 1; - //block mask = floor(s / 2) ones in binary - let b_mask = 1 << down - 1; - // data blocks in even super blocks before current = 2 ** ceil(s / 2) - // data blocks in odd super blocks before current = 2 ** floor(s / 2) - // data blocks before the super block = element mask + block mask - // elements before the super block = 2 ** s - // first floor(s / 2) bits in index after the highest bit = index of data block in super block - // the next ceil(s / 2) to the end of binary representation of index + 1 = index of element in data block - (Nat32.toNat(e_mask + b_mask + 2 + (i >> up) & b_mask), Nat32.toNat(i & e_mask)) -}; - -// this was optimized in terms of instructions -func locate_optimal(index : Nat) : (Nat, Nat) { - // super block s = bit length - 1 = (32 - leading zeros) - 1 - // blocks before super block s == 2 ** s - let i = Nat32.fromNat(index); - let lz = Nat32.bitcountLeadingZero(i); - let lz2 = lz >> 1; - // we split into cases to apply different optimizations in each one - if (lz & 1 == 0) { - // ceil(s / 2) = 16 - lz2 - // floor(s / 2) = 15 - lz2 - // i in binary = zeroes; 1; bits blocks mask; bits element mask - // bit lengths = lz; 1; 15 - lz2; 16 - lz2 - // blocks before = 2 ** ceil(s / 2) + 2 ** floor(s / 2) - - // so in order to calculate index of the data block - // we need to shift i by 16 - lz2 and set bit with number 16 - lz2, bit 15 - lz2 is already set - - // element mask = 2 ** (16 - lz2) = (1 << 16) >> lz2 = 0xFFFF >> lz2 - let mask = 0xFFFF >> lz2; - (Nat32.toNat(((i << lz2) >> 16) ^ (0x10000 >> lz2)), Nat32.toNat(i & mask)) - } else { - // s / 2 = ceil(s / 2) = floor(s / 2) = 15 - lz2 - // i in binary = zeroes; 1; bits blocks mask; bits element mask - // bit lengths = lz; 1; 15 - lz2; 15 - lz2 - // block mask = element mask = mask = 2 ** (s / 2) - 1 = 2 ** (15 - lz2) - 1 = (1 << 15) >> lz2 = 0x7FFF >> lz2 - // blocks before = 2 * 2 ** (s / 2) - - // so in order to calculate index of the data block - // we need to shift i by 15 - lz2, set bit with number 16 - lz2 and unset bit 15 - lz2 - - let mask = 0x7FFF >> lz2; - (Nat32.toNat(((i << lz2) >> 15) ^ (0x18000 >> lz2)), Nat32.toNat(i & mask)) - } -}; - -let locate_n = 1_000; -var i = 0; -while (i < locate_n) { - assert (locate_readable(i) == locate_optimal(i)); - assert (locate_readable(1_000_000 + i) == locate_optimal(1_000_000 + i)); - assert (locate_readable(1_000_000_000 + i) == locate_optimal(1_000_000_000 + i)); - assert (locate_readable(2_000_000_000 + i) == locate_optimal(2_000_000_000 + i)); - assert (locate_readable(2 ** 32 - 1 - i : Nat) == locate_optimal(2 ** 32 - 1 - i : Nat)); - i += 1 -}; - // Claude tests (from original Mops package) // Helper function to run tests From 1bb7e70ad14a92029d800723d76504bfc02176c5 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 4 Oct 2025 17:37:11 +0300 Subject: [PATCH 114/123] Fixes in response to comments. --- src/List.mo | 49 ++++++++++++++++++++++------------------------- test/List.test.mo | 10 +++++----- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/List.mo b/src/List.mo index ed08944a5..cf150a89f 100644 --- a/src/List.mo +++ b/src/List.mo @@ -14,12 +14,9 @@ import PureList "pure/List"; import Prim "mo:⛔"; -import Result "Result"; import Nat32 "Nat32"; import Array "Array"; -import Iter "Iter"; import Nat "Nat"; -import Order "Order"; import Option "Option"; import VarArray "VarArray"; import Types "Types"; @@ -369,7 +366,7 @@ module { /// Runtime: O(number of elements in list) /// /// Space: O(number of elements in list) - public func join(lists : Iter.Iter>) : List { + public func join(lists : Types.Iter>) : List { var result = empty(); for (list in lists) { reserve(result, size(list)); @@ -558,7 +555,7 @@ module { /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E> { + public func mapResult(list : List, f : T -> Types.Result) : Types.Result, E> { var error : ?E = null; let blocks = VarArray.repeat<[var ?R]>([var], list.blocks.size()); @@ -706,7 +703,7 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `k` runs in O(1) time and space. - public func flatMap(list : List, k : T -> Iter.Iter) : List { + public func flatMap(list : List, k : T -> Types.Iter) : List { let result = empty(); let blocks = list.blocks; @@ -1045,7 +1042,7 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func sortInPlace(list : List, compare : (T, T) -> Order.Order) { + public func sortInPlace(list : List, compare : (T, T) -> Types.Order) { if (size(list) < 2) return; let array = toVarArray(list); @@ -1089,7 +1086,7 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func sort(list : List, compare : (T, T) -> Order.Order) : List { + public func sort(list : List, compare : (T, T) -> Types.Order) : List { let array = toVarArray(list); VarArray.sortInPlace(array, compare); fromVarArray(array) @@ -1117,7 +1114,7 @@ module { /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat { if (isEmpty(list)) return null; - nextIndexOf(list, element, 0, equal) + nextIndexOf(list, equal, element, 0) }; /// Returns the index of the next occurence of `element` in the `list` starting from the `from` index (inclusive). @@ -1137,7 +1134,7 @@ module { /// Space: O(1) /// /// *Runtime and space assumes that `equal` runs in O(1) time and space. - public func nextIndexOf(list : List, element : T, fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat { + public func nextIndexOf(list : List, equal : (T, T) -> Bool, element : T, fromInclusive : Nat) : ?Nat { if (fromInclusive >= size(list)) Prim.trap "List index out of bounds in nextIndexOf"; let (blockIndex, elementIndex) = locate(fromInclusive); @@ -1182,9 +1179,9 @@ module { /// *Runtime and space assumes that `equal` runs in `O(1)` time and space. public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat = prevIndexOf( list, + equal, element, - size(list), - equal + size(list) ); /// Returns the index of the previous occurence of `element` in the `list` starting from the `from` index (exclusive). @@ -1201,7 +1198,7 @@ module { /// Runtime: O(size) /// /// Space: O(1) - public func prevIndexOf(list : List, element : T, fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat { + public func prevIndexOf(list : List, equal : (T, T) -> Bool, element : T, fromExclusive : Nat) : ?Nat { if (fromExclusive > size(list)) Prim.trap "List index out of bounds in prevIndexOf"; let blocks = list.blocks; @@ -1349,7 +1346,7 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in `O(1)` time and space. - public func binarySearch(list : List, compare : (T, T) -> Order.Order, element : T) : { + public func binarySearch(list : List, compare : (T, T) -> Types.Order, element : T) : { #found : Nat; #insertionIndex : Nat } { @@ -1512,7 +1509,7 @@ module { /// List, then this may lead to unexpected results. /// /// Runtime: `O(1)` - public func values(list : List) : Iter.Iter = object { + public func values(list : List) : Types.Iter = object { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; @@ -1559,7 +1556,7 @@ module { /// Runtime: `O(1)` /// /// Warning: Allocates memory on the heap to store ?(Nat, T). - public func enumerate(list : List) : Iter.Iter<(Nat, T)> = object { + public func enumerate(list : List) : Types.Iter<(Nat, T)> = object { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; @@ -1610,7 +1607,7 @@ module { /// List, then this may lead to unexpected results. /// /// Runtime: `O(1)` - public func reverseValues(list : List) : Iter.Iter = object { + public func reverseValues(list : List) : Types.Iter = object { var blockIndex = list.blockIndex; var elementIndex = list.elementIndex; var db : [var ?T] = if (blockIndex < list.blocks.size()) { @@ -1652,7 +1649,7 @@ module { /// Runtime: `O(1)` /// /// Warning: Allocates memory on the heap to store ?(T, Nat). - public func reverseEnumerate(list : List) : Iter.Iter<(Nat, T)> = object { + public func reverseEnumerate(list : List) : Types.Iter<(Nat, T)> = object { var i = size(list); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex; @@ -1698,7 +1695,7 @@ module { /// List, then this may lead to unexpected results. /// /// Runtime: `O(1)` - public func keys(list : List) : Iter.Iter = Nat.range(0, size(list)); + public func keys(list : List) : Types.Iter = Nat.range(0, size(list)); /// Creates a new List containing all elements from the provided iterator. /// Elements are added in the order they are returned by the iterator. @@ -1716,7 +1713,7 @@ module { /// ``` /// /// Runtime: `O(size)` - public func fromIter(iter : Iter.Iter) : List { + public func fromIter(iter : Types.Iter) : List { let list = empty(); for (element in iter) add(list, element); list @@ -1741,7 +1738,7 @@ module { /// The maximum number of elements in a `List` is 2^32. /// /// Runtime: `O(size)`, where n is the size of iter. - public func addAll(list : List, iter : Iter.Iter) { + public func addAll(list : List, iter : Types.Iter) { for (element in iter) add(list, element) }; @@ -2086,7 +2083,7 @@ module { /// Runtime: O(1) /// /// Space: O(1) - public func range(list : List, fromInclusive : Int, toExclusive : Int) : Iter.Iter = object { + public func range(list : List, fromInclusive : Int, toExclusive : Int) : Types.Iter = object { let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); let blocks = list.blocks.size(); var blockIndex = 0; @@ -2134,7 +2131,7 @@ module { /// Runtime: O(toExclusive - fromInclusive) /// /// Space: O(toExclusive - fromInclusive) - public func subArray(list : List, fromInclusive : Int, toExclusive : Int) : [T] { + public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T] { let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); let blocks = list.blocks.size(); var blockIndex = 0; @@ -2300,7 +2297,7 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func max(list : List, compare : (T, T) -> Order.Order) : ?T { + public func max(list : List, compare : (T, T) -> Types.Order) : ?T { var maxSoFar : T = switch (first(list)) { case (?x) x; case null return null @@ -2352,7 +2349,7 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func min(list : List, compare : (T, T) -> Order.Order) : ?T { + public func min(list : List, compare : (T, T) -> Types.Order) : ?T { var minSoFar : T = switch (first(list)) { case (?x) x; case null return null @@ -2454,7 +2451,7 @@ module { /// Space: `O(1)` /// /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order { + public func compare(list1 : List, list2 : List, compare : (T, T) -> Types.Order) : Types.Order { let blocks1 = list1.blocks; let blocks2 = list2.blocks; let blockCount = Nat.min(blocks1.size(), blocks2.size()); diff --git a/test/List.test.mo b/test/List.test.mo index 8eaf98614..1ee8ca5ef 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1295,12 +1295,12 @@ func testRange(n : Nat) : Bool { true }; -func testSubArray(n : Nat) : Bool { +func testSliceToArray(n : Nat) : Bool { if (n > 10) return true; // Skip large ranges for performance let vec = List.fromArray(Array.tabulate(n, func(i) = i)); for (left in Nat.range(0, n)) { for (right in Nat.range(left, n + 1)) { - let slice = List.subArray(vec, left, right); + let slice = List.sliceToArray(vec, left, right); let expected = Array.tabulate(right - left, func(i) = left + i); if (slice != expected) { Debug.print( @@ -1656,7 +1656,7 @@ func testNextIndexOf(n : Nat) : Bool { let vec = List.tabulate(n, func(i) = i); for (from in Nat.range(0, n)) { for (element in Nat.range(0, n + 1)) { - let actual = List.nextIndexOf(vec, element, from, Nat.equal); + let actual = List.nextIndexOf(vec, Nat.equal, element, from); let expected = nextIndexOf(vec, element, from); if (expected != actual) { Debug.print( @@ -1686,7 +1686,7 @@ func testPrevIndexOf(n : Nat) : Bool { let vec = List.tabulate(n, func(i) = i); for (from in Nat.range(0, n + 1)) { for (element in Nat.range(0, n + 1)) { - let actual = List.prevIndexOf(vec, element, from, Nat.equal); + let actual = List.prevIndexOf(vec, Nat.equal, element, from); let expected = prevIndexOf(vec, element, from); if (expected != actual) { Debug.print( @@ -1762,7 +1762,7 @@ func runAllTests() { runTest("testMapInPlace", testMapInPlace); runTest("testFlatMap", testFlatMap); runTest("testRange", testRange); - runTest("testSubArray", testSubArray); + runTest("testSliceToArray", testSliceToArray); runTest("testIndexOf", testIndexOf); runTest("testLastIndexOf", testLastIndexOf); runTest("testContains", testContains); From ff94cc1ff0cf60c08ec9265122ce998faeb41c3a Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 4 Oct 2025 17:46:44 +0300 Subject: [PATCH 115/123] Added sliceToVarArray --- src/List.mo | 68 ++++++++++++++++++++++++++++++++--------------- test/List.test.mo | 4 ++- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/List.mo b/src/List.mo index cf150a89f..a4c3a9c66 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2115,24 +2115,9 @@ module { } }; - /// Returns a new array containing elements from `list` starting at index `fromInclusive` up to (but not including) index `toExclusive`. - /// If the indices are out of bounds, they are clamped to the array bounds. - /// - /// ```motoko include=import - /// let array = List.fromArray([1, 2, 3, 4, 5]); - /// - /// let slice1 = List.subArray(array, 1, 4); - /// assert slice1 == [2, 3, 4]; - /// - /// let slice2 = List.subArray(array, 1, -1); - /// assert slice2 == [2, 3, 4]; - /// ``` - /// - /// Runtime: O(toExclusive - fromInclusive) - /// - /// Space: O(toExclusive - fromInclusive) - public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T] { - let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); + func sliceToArrayBase(list : List, start : Nat) : { + next(i : Nat) : T + } = object { let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; @@ -2144,7 +2129,7 @@ module { var db : [var ?T] = list.blocks[blockIndex]; var dbSize = db.size(); - func generator(i : Nat) : T { + public func next(i : Nat) : T { if (elementIndex == dbSize) { blockIndex += 1; if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); @@ -2160,8 +2145,49 @@ module { }; case null Prim.trap(INTERNAL_ERROR) } - }; - Array.tabulate(end - start, generator) + } + }; + + /// Returns a new array containing elements from `list` starting at index `fromInclusive` up to (but not including) index `toExclusive`. + /// If the indices are out of bounds, they are clamped to the array bounds. + /// + /// ```motoko include=import + /// let array = List.fromArray([1, 2, 3, 4, 5]); + /// + /// let slice1 = List.sliceToArray(array, 1, 4); + /// assert slice1 == [2, 3, 4]; + /// + /// let slice2 = List.sliceToArray(array, 1, -1); + /// assert slice2 == [2, 3, 4]; + /// ``` + /// + /// Runtime: O(toExclusive - fromInclusive) + /// + /// Space: O(toExclusive - fromInclusive) + public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T] { + let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); + Array.tabulate(end - start, sliceToArrayBase(list, start).next) + }; + + /// Returns a new var array containing elements from `list` starting at index `fromInclusive` up to (but not including) index `toExclusive`. + /// If the indices are out of bounds, they are clamped to the array bounds. + /// + /// ```motoko include=import + /// let array = List.fromArray([1, 2, 3, 4, 5]); + /// + /// let slice1 = List.sliceToVarArray(array, 1, 4); + /// assert slice1 == [var 2, 3, 4]; + /// + /// let slice2 = List.sliceToVarArray(array, 1, -1); + /// assert slice2 == [var 2, 3, 4]; + /// ``` + /// + /// Runtime: O(toExclusive - fromInclusive) + /// + /// Space: O(toExclusive - fromInclusive) + public func sliceToVarArray(list : List, fromInclusive : Int, toExclusive : Int) : [var T] { + let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); + VarArray.tabulate(end - start, sliceToArrayBase(list, start).next) }; /// Like `forEachEntryRev` but iterates through the list in reverse order, diff --git a/test/List.test.mo b/test/List.test.mo index 1ee8ca5ef..1059aeaaa 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -1301,8 +1301,10 @@ func testSliceToArray(n : Nat) : Bool { for (left in Nat.range(0, n)) { for (right in Nat.range(left, n + 1)) { let slice = List.sliceToArray(vec, left, right); + let sliceVar = List.sliceToVarArray(vec, left, right); let expected = Array.tabulate(right - left, func(i) = left + i); - if (slice != expected) { + let expectedVar = VarArray.tabulate(right - left, func(i) = left + i); + if (slice != expected or not VarArray.equal(sliceVar, expectedVar, Nat.equal)) { Debug.print( "Slice mismatch for left = " # Nat.toText(left) # ", right = " # Nat.toText(right) # ": expected " # debug_show (expected) # ", got " # debug_show (slice) ); From 820c916f5d1ecb0da9b37504d7a0e8c96a97a46c Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 5 Oct 2025 15:37:49 +0300 Subject: [PATCH 116/123] Simplify sliceToArrayBase, fix CI --- src/List.mo | 35 ++++++++++++++---------------- validation/api/api.lock.json | 41 ++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/List.mo b/src/List.mo index a4c3a9c66..8309b0f1f 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1122,11 +1122,11 @@ module { /// ```motoko include=import /// import Char "mo:core/Char"; /// let list = List.fromArray(['c', 'o', 'f', 'f', 'e', 'e']); - /// assert List.nextIndexOf(list, 'c', 0, Char.equal) == ?0; - /// assert List.nextIndexOf(list, 'f', 0, Char.equal) == ?2; - /// assert List.nextIndexOf(list, 'f', 2, Char.equal) == ?2; - /// assert List.nextIndexOf(list, 'f', 3, Char.equal) == ?3; - /// assert List.nextIndexOf(list, 'f', 4, Char.equal) == null; + /// assert List.nextIndexOf(list, Char.equal, 'c', 0) == ?0; + /// assert List.nextIndexOf(list, Char.equal, 'f', 0) == ?2; + /// assert List.nextIndexOf(list, Char.equal, 'f', 2) == ?2; + /// assert List.nextIndexOf(list, Char.equal, 'f', 3) == ?3; + /// assert List.nextIndexOf(list, Char.equal, 'f', 4) == null; /// ``` /// /// Runtime: O(size) @@ -1189,10 +1189,10 @@ module { /// ```motoko include=import /// import Char "mo:core/Char"; /// let list = List.fromArray(['c', 'o', 'f', 'f', 'e', 'e']); - /// assert List.prevIndexOf(list, 'c', List.size(list), Char.equal) == ?0; - /// assert List.prevIndexOf(list, 'e', List.size(list), Char.equal) == ?5; - /// assert List.prevIndexOf(list, 'e', 5, Char.equal) == ?4; - /// assert List.prevIndexOf(list, 'e', 4, Char.equal) == null; + /// assert List.prevIndexOf(list, Char.equal, 'c', List.size(list)) == ?0; + /// assert List.prevIndexOf(list, Char.equal, 'e', List.size(list)) == ?5; + /// assert List.prevIndexOf(list, Char.equal, 'e', 5) == ?4; + /// assert List.prevIndexOf(list, Char.equal, 'e', 4) == null; /// ``` /// /// Runtime: O(size) @@ -1671,7 +1671,7 @@ module { i -= 1; return ?(i, x) }; - case (_) Prim.trap(INTERNAL_ERROR) + case (_) Prim.trap INTERNAL_ERROR } } }; @@ -1763,10 +1763,10 @@ module { func generator(_ : Nat) : T { if (elementIndex == sz) { blockIndex += 1; - if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); + if (blockIndex >= blocks) Prim.trap INTERNAL_ERROR; db := list.blocks[blockIndex]; sz := db.size(); - if (sz == 0) Prim.trap(INTERNAL_ERROR); + if (sz == 0) Prim.trap INTERNAL_ERROR; elementIndex := 0 }; switch (db[elementIndex]) { @@ -1774,7 +1774,7 @@ module { elementIndex += 1; return x }; - case (_) Prim.trap(INTERNAL_ERROR) + case (_) Prim.trap INTERNAL_ERROR } }; @@ -2118,7 +2118,6 @@ module { func sliceToArrayBase(list : List, start : Nat) : { next(i : Nat) : T } = object { - let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; if (start != 0) { @@ -2132,10 +2131,8 @@ module { public func next(i : Nat) : T { if (elementIndex == dbSize) { blockIndex += 1; - if (blockIndex >= blocks) Prim.trap(INTERNAL_ERROR); db := list.blocks[blockIndex]; dbSize := db.size(); - if (dbSize == 0) Prim.trap(INTERNAL_ERROR); elementIndex := 0 }; switch (db[elementIndex]) { @@ -2143,7 +2140,7 @@ module { elementIndex += 1; return x }; - case null Prim.trap(INTERNAL_ERROR) + case null Prim.trap INTERNAL_ERROR } } }; @@ -2176,10 +2173,10 @@ module { /// let array = List.fromArray([1, 2, 3, 4, 5]); /// /// let slice1 = List.sliceToVarArray(array, 1, 4); - /// assert slice1 == [var 2, 3, 4]; + /// assert VarArray.equal(slice1, [var 2, 3, 4], Nat.equal); /// /// let slice2 = List.sliceToVarArray(array, 1, -1); - /// assert slice2 == [var 2, 3, 4]; + /// assert VarArray.equal(slice2, [var 2, 3, 4], Nat.equal); /// ``` /// /// Runtime: O(toExclusive - fromInclusive) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 31cfeaf12..a50972406 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -511,18 +511,18 @@ "name": "List", "exports": [ "public func add(list : List, element : T)", - "public func addAll(list : List, iter : Iter.Iter)", + "public func addAll(list : List, iter : Types.Iter)", "public func addRepeat(list : List, initValue : T, count : Nat)", "public func all(list : List, predicate : T -> Bool) : Bool", "public func any(list : List, predicate : T -> Bool) : Bool", "public func at(list : List, index : Nat) : T", - "public func binarySearch(list : List, compare : (T, T) -> Order.Order, element : T) : { #found : Nat; #insertionIndex : Nat }", + "public func binarySearch(list : List, compare : (T, T) -> Types.Order, element : T) : { #found : Nat; #insertionIndex : Nat }", "public func clear(list : List)", "public func clone(list : List) : List", - "public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order", + "public func compare(list1 : List, list2 : List, compare : (T, T) -> Types.Order) : Types.Order", "public func contains(list : List, equal : (T, T) -> Bool, element : T) : Bool", "public func empty() : List", - "public func enumerate(list : List) : Iter.Iter<(Nat, T)>", + "public func enumerate(list : List) : Types.Iter<(Nat, T)>", "public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool", "public func filter(list : List, predicate : T -> Bool) : List", "public func filterMap(list : List, f : T -> ?R) : List", @@ -530,53 +530,54 @@ "public func findIndex(list : List, predicate : T -> Bool) : ?Nat", "public func findLastIndex(list : List, predicate : T -> Bool) : ?Nat", "public func first(list : List) : ?T", - "public func flatMap(list : List, k : T -> Iter.Iter) : List", + "public func flatMap(list : List, k : T -> Types.Iter) : List", "public func flatten(lists : List>) : List", "public func foldLeft(list : List, base : A, combine : (A, T) -> A) : A", "public func foldRight(list : List, base : A, combine : (T, A) -> A) : A", "public func forEach(list : List, f : T -> ())", "public func forEachEntry(list : List, f : (Nat, T) -> ())", "public func fromArray(array : [T]) : List", - "public func fromIter(iter : Iter.Iter) : List", + "public func fromIter(iter : Types.Iter) : List", "public func fromPure(pure : PureList.List) : List", "public func fromVarArray(array : [var T]) : List", "public func get(list : List, index : Nat) : ?T", "public func indexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat", "public func isEmpty(list : List) : Bool", - "public func join(lists : Iter.Iter>) : List", - "public func keys(list : List) : Iter.Iter", + "public func join(lists : Types.Iter>) : List", + "public func keys(list : List) : Types.Iter", "public func last(list : List) : ?T", "public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat", "public type List", "public func map(list : List, f : T -> R) : List", "public func mapEntries(list : List, f : (T, Nat) -> R) : List", "public func mapInPlace(list : List, f : T -> T)", - "public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E>", - "public func max(list : List, compare : (T, T) -> Order.Order) : ?T", - "public func min(list : List, compare : (T, T) -> Order.Order) : ?T", - "public func nextIndexOf(list : List, element : T, fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat", - "public func prevIndexOf(list : List, element : T, fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat", + "public func mapResult(list : List, f : T -> Types.Result) : Types.Result, E>", + "public func max(list : List, compare : (T, T) -> Types.Order) : ?T", + "public func min(list : List, compare : (T, T) -> Types.Order) : ?T", + "public func nextIndexOf(list : List, equal : (T, T) -> Bool, element : T, fromInclusive : Nat) : ?Nat", + "public func prevIndexOf(list : List, equal : (T, T) -> Bool, element : T, fromExclusive : Nat) : ?Nat", "public func put(list : List, index : Nat, value : T)", - "public func range(list : List, fromInclusive : Int, toExclusive : Int) : Iter.Iter", + "public func range(list : List, fromInclusive : Int, toExclusive : Int) : Types.Iter", "public func removeLast(list : List) : ?T", "public func repeat(initValue : T, size : Nat) : List", "public func reverse(list : List) : List", - "public func reverseEnumerate(list : List) : Iter.Iter<(Nat, T)>", + "public func reverseEnumerate(list : List) : Types.Iter<(Nat, T)>", "public func reverseForEach(list : List, f : T -> ())", "public func reverseForEachEntry(list : List, f : (Nat, T) -> ())", "public func reverseInPlace(list : List)", - "public func reverseValues(list : List) : Iter.Iter", + "public func reverseValues(list : List) : Types.Iter", "public func singleton(element : T) : List", "public func size(list : List) : Nat", - "public func sort(list : List, compare : (T, T) -> Order.Order) : List", - "public func sortInPlace(list : List, compare : (T, T) -> Order.Order)", - "public func subArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", + "public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", + "public func sliceToVarArray(list : List, fromInclusive : Int, toExclusive : Int) : [var T]", + "public func sort(list : List, compare : (T, T) -> Types.Order) : List", + "public func sortInPlace(list : List, compare : (T, T) -> Types.Order)", "public func tabulate(size : Nat, generator : Nat -> T) : List", "public func toArray(list : List) : [T]", "public func toPure(list : List) : PureList.List", "public func toText(list : List, f : T -> Text) : Text", "public func toVarArray(list : List) : [var T]", - "public func values(list : List) : Iter.Iter" + "public func values(list : List) : Types.Iter" ] }, { From 26fd4a5abd11df4c5aab95464841773ff511d2d8 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 5 Oct 2025 15:41:49 +0300 Subject: [PATCH 117/123] Fix. --- src/List.mo | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/List.mo b/src/List.mo index 8309b0f1f..e466253aa 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2170,6 +2170,8 @@ module { /// If the indices are out of bounds, they are clamped to the array bounds. /// /// ```motoko include=import + /// import VarArray "mo:core/VarArray"; + /// /// let array = List.fromArray([1, 2, 3, 4, 5]); /// /// let slice1 = List.sliceToVarArray(array, 1, 4); From f2ea9ecdad03b66844c501126c25e461cceadd2b Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sun, 5 Oct 2025 15:46:00 +0300 Subject: [PATCH 118/123] Fix. --- src/List.mo | 1 + 1 file changed, 1 insertion(+) diff --git a/src/List.mo b/src/List.mo index e466253aa..434b51dc8 100644 --- a/src/List.mo +++ b/src/List.mo @@ -2171,6 +2171,7 @@ module { /// /// ```motoko include=import /// import VarArray "mo:core/VarArray"; + /// import Nat "mo:core/Nat"; /// /// let array = List.fromArray([1, 2, 3, 4, 5]); /// From 3855fa05b2ee5e1b1dbd182dca092c8c41ca2a49 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Mon, 6 Oct 2025 17:28:35 +0300 Subject: [PATCH 119/123] Simplify toArray. --- src/List.mo | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/List.mo b/src/List.mo index 434b51dc8..1345e7806 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1754,7 +1754,6 @@ module { /// /// Runtime: `O(size)` public func toArray(list : List) : [T] { - let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; var sz = 0; @@ -1763,10 +1762,8 @@ module { func generator(_ : Nat) : T { if (elementIndex == sz) { blockIndex += 1; - if (blockIndex >= blocks) Prim.trap INTERNAL_ERROR; db := list.blocks[blockIndex]; sz := db.size(); - if (sz == 0) Prim.trap INTERNAL_ERROR; elementIndex := 0 }; switch (db[elementIndex]) { From 6efd7a63dbc3cc55f1fca99b8d9adcf338e16384 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 7 Oct 2025 19:59:51 +0300 Subject: [PATCH 120/123] Remove sortInPlace. --- src/List.mo | 24 ++---------------------- test/List.test.mo | 4 ++-- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/src/List.mo b/src/List.mo index 1345e7806..420efc5e7 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1034,7 +1034,7 @@ module { /// List.add(list, 3); /// List.add(list, 1); /// List.add(list, 2); - /// List.sortInPlace(list, Nat.compare); + /// List.sort(list, Nat.compare); /// assert List.toArray(list) == [1, 2, 3]; /// ``` /// @@ -1042,7 +1042,7 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func sortInPlace(list : List, compare : (T, T) -> Types.Order) { + public func sort(list : List, compare : (T, T) -> Types.Order) { if (size(list) < 2) return; let array = toVarArray(list); @@ -1072,26 +1072,6 @@ module { } }; - /// Sorts the elements in the list according to `compare`. - /// Sort is deterministic and stable. - /// - /// ```motoko include=import - /// import Nat "mo:core/Nat"; - /// - /// let list = List.fromArray([4, 2, 6]); - /// let sorted = List.sort(list, Nat.compare); - /// assert List.toArray(sorted) == [2, 4, 6]; - /// ``` - /// Runtime: O(size * log(size)) - /// - /// Space: O(size) - /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func sort(list : List, compare : (T, T) -> Types.Order) : List { - let array = toVarArray(list); - VarArray.sortInPlace(array, compare); - fromVarArray(array) - }; - /// Finds the first index of `element` in `list` using equality of elements defined /// by `equal`. Returns `null` if `element` is not found. /// diff --git a/test/List.test.mo b/test/List.test.mo index 1059aeaaa..ab48d498a 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -963,7 +963,7 @@ run( [ test( "sort", - List.sortInPlace(list, Nat.compare) |> List.toArray(list), + List.sort(list, Nat.compare) |> List.toArray(list), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |> M.equals(T.array(T.natTestable, _)) ) ] @@ -1391,7 +1391,7 @@ func testReverse(n : Nat) : Bool { func testSort(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(n, func(i) = Int.abs((i * 123) % 100 - 50))); - List.sortInPlace(vec, Nat.compare); + List.sort(vec, Nat.compare); assertValid(vec); List.equal(vec, List.fromArray(Array.sort(Array.tabulate(n, func(i) = Int.abs((i * 123) % 100 - 50)), Nat.compare)), Nat.equal) }; From 6a8ddd3000a7e4a9a3cc323d6a7fc864a46f69e5 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Tue, 7 Oct 2025 20:00:59 +0300 Subject: [PATCH 121/123] Fix. --- validation/api/api.lock.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index a50972406..44bb9b96b 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -570,8 +570,7 @@ "public func size(list : List) : Nat", "public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", "public func sliceToVarArray(list : List, fromInclusive : Int, toExclusive : Int) : [var T]", - "public func sort(list : List, compare : (T, T) -> Types.Order) : List", - "public func sortInPlace(list : List, compare : (T, T) -> Types.Order)", + "public func sort(list : List, compare : (T, T) -> Types.Order)", "public func tabulate(size : Nat, generator : Nat -> T) : List", "public func toArray(list : List) : [T]", "public func toPure(list : List) : PureList.List", From 398ed1924eef33d12785d27948fad645a9ab903a Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 18 Oct 2025 17:20:57 +0300 Subject: [PATCH 122/123] Fix. --- src/List.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/List.mo b/src/List.mo index 3f52aa170..60e5ab147 100644 --- a/src/List.mo +++ b/src/List.mo @@ -1042,7 +1042,7 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `compare` runs in O(1) time and space. - public func sortInPlace(list : List, compare : (T, T) -> Order.Order) { + public func sortInPlace(list : List, compare : (T, T) -> Types.Order) { if (size(list) < 2) return; let array = toVarArray(list); From 728b7e1867dcb5270aea66f2f8253423611dba62 Mon Sep 17 00:00:00 2001 From: Andrii Stepanov Date: Sat, 18 Oct 2025 17:24:38 +0300 Subject: [PATCH 123/123] Fix. --- Changelog.md | 2 +- validation/api/api.lock.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 75ae9fa4d..020046a82 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,11 +1,11 @@ ## Next +* **Breaking:** Rename `sort` to `sortInPlace`, add `sort` (#405). * Add `isCleanReject` to `Error`, align reject code order with IC interface specification and improve comments (#401). * internal: updates `matchers` dev-dependency (#394). * Add `PriorityQueue` (#392). * Add support for Weak references (#388). * Clarify difference between `List` and `pure/List` in doc comments (#386). -* **Breaking:** Rename `sort` to `sortInPlace`, add `sort` (#405). * Optimize methods in `List` (#337). ## 1.0.0 diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index fbf365a18..a50972406 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -568,8 +568,11 @@ "public func reverseValues(list : List) : Types.Iter", "public func singleton(element : T) : List", "public func size(list : List) : Nat", + "public func sliceToArray(list : List, fromInclusive : Int, toExclusive : Int) : [T]", + "public func sliceToVarArray(list : List, fromInclusive : Int, toExclusive : Int) : [var T]", "public func sort(list : List, compare : (T, T) -> Types.Order) : List", - "public func sortInPlace(list : List, compare : (T, T) -> Order.Order)", + "public func sortInPlace(list : List, compare : (T, T) -> Types.Order)", + "public func tabulate(size : Nat, generator : Nat -> T) : List", "public func toArray(list : List) : [T]", "public func toPure(list : List) : PureList.List", "public func toText(list : List, f : T -> Text) : Text",