diff --git a/Changelog.md b/Changelog.md index 228e6abe..020046a8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,11 +1,12 @@ ## 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 @@ -87,4 +88,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 0ec82f7b..60e5ab14 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 @@ -16,9 +16,7 @@ import PureList "pure/List"; import Prim "mo:⛔"; import Nat32 "Nat32"; import Array "Array"; -import Iter "Iter"; import Nat "Nat"; -import Order "Order"; import Option "Option"; import VarArray "VarArray"; import Types "Types"; @@ -42,7 +40,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 }; @@ -60,46 +61,52 @@ 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 { + 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 - }; - data_blocks[i] := block + if (elementIndex != 0) { + dataBlocks[blockIndex] := if (Option.isNull(initValue)) VarArray.repeat( + null, + dataBlockSize(blockIndex) + ) else VarArray.tabulate( + dataBlockSize(blockIndex), + func i = if (i < elementIndex) initValue else null + ) }; { - 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,84 +119,134 @@ module { /// /// Space: `O(size)` public func toPure(list : List) : PureList.List { - PureList.fromIter(values(list)) // TODO: optimize + var result : PureList.List = null; + + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = blockIndex; + if (elementIndex == 0) i -= 1; + + 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 + } + }; + i -= 1 + }; + + result }; - /// Converts a purely functional `List` to a mutable `List`. + /// Converts a purely functional `PureList` 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)` /// /// Space: `O(size)` public func fromPure(pure : PureList.List) : List { - let list = empty(); - PureList.forEach(pure, func(x) = add(list, x)); - list + var p = pure; + var list = empty(); + loop { + switch (p) { + case (?(x, xs)) { + add(list, x); + p := 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) { - let (blockIndex, elementIndex) = locate(size(list) + count); - let blocks = new_index_block_length(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; - list.blocks := VarArray.repeat<[var ?T]>([var], blocks); + 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 oldBlocksCount = list.blocks.size(); + if (oldBlocksCount < blocksCount) { + let oldBlocks = list.blocks; + let blocks = VarArray.repeat<[var ?T]>([var], blocksCount); var i = 0; - while (i < old_blocks) { - list.blocks[i] := old_data_blocks[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 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; - list.blockIndex += 1 - } else { - if (list.blocks[list.blockIndex].size() == 0) { - list.blocks[list.blockIndex] := VarArray.repeat(null, db_size) + 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 = list.elementIndex; - let to = Nat.min(list.elementIndex + cnt, db_size); + blocks[blockIndex] := VarArray.repeat(null, dbSize) + }; - let block = list.blocks[list.blockIndex]; - var i = from; - while (i < to) { - block[i] := ?initValue; - i += 1 - }; + let block = blocks[blockIndex]; + let dbSize = block.size(); + let to = Nat.min(elementIndex + cnt, dbSize); + cnt -= to - elementIndex; - list.elementIndex := to; - if (list.elementIndex == db_size) { - list.elementIndex := 0; - list.blockIndex += 1 - }; - cnt -= to - from + while (elementIndex < to) { + block[elementIndex] := initValue; + elementIndex += 1 + }; + + if (elementIndex == dbSize) { + elementIndex := 0; + blockIndex += 1 } - } + }; + + list.blockIndex := blockIndex; + list.elementIndex := elementIndex + }; + + 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 + /// 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: @@ -209,6 +266,115 @@ 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); + + var i = 1; + var pos = 0; + + while (i < blockIndex) { + let len = dataBlockSize(i); + dataBlocks[i] := VarArray.tabulate(len, func i = ?generator(pos + i)); + pos += len; + i += 1 + }; + if (elementIndex != 0 and blockIndex < blocks) { + dataBlocks[i] := VarArray.tabulate( + dataBlockSize(blockIndex), + func i = if (i < elementIndex) ?generator(pos + i) else null + ) + }; + + { + var blocks = dataBlocks; + var blockIndex = blockIndex; + var elementIndex = elementIndex + } + }; + + /// 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"; + /// + /// 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 + }; + + /// 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 : Types.Iter>) : List { + var result = empty(); + for (list in lists) { + reserve(result, size(list)); + forEach(list, func item = addUnsafe(result, item)) + }; + result + }; + /// Returns a copy of a List, with the same size. /// /// Example: @@ -223,11 +389,11 @@ module { /// Runtime: `O(size)` 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] - ) + Nat.min( + newIndexBlockLength(Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex)), + list.blocks.size() + ), + func(i) = VarArray.clone(list.blocks[i]) ); var blockIndex = list.blockIndex; var elementIndex = list.elementIndex @@ -246,22 +412,197 @@ 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 blocksCount = Nat.min( + newIndexBlockLength(Nat32.fromNat(if (list.elementIndex == 0) list.blockIndex - 1 else list.blockIndex)), + list.blocks.size() ); - var blockIndex = list.blockIndex; - var elementIndex = list.elementIndex + let blocks = VarArray.repeat<[var ?R]>([var], blocksCount); + + var i = 1; + label l 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 break l + }; + j += 1 + }; + i += 1 + }; + + { + var blocks = blocks; + var blockIndex = list.blockIndex; + var elementIndex = list.elementIndex + } + }; + + /// 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; + label l 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 break l + }; + 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 -> Types.Result) : Types.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. @@ -280,12 +621,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,30 +662,75 @@ 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 }; - /// 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 { - let d = Nat32.fromNat(list.blockIndex); - let i = Nat32.fromNat(list.elementIndex); + /// 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 -> Types.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 + }; + + 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. @@ -351,47 +752,65 @@ 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(elementIndex)) + }; + + /// 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 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 +835,37 @@ 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]; + + lastDataBlock[elementIndex] := ?element; + + elementIndex += 1; + if (elementIndex == lastDataBlock.size()) { + elementIndex := 0; + list.blockIndex += 1 + }; + list.elementIndex := elementIndex + }; - last_data_block[elementIndex] := ?element; + private func addUnsafe(list : List, element : T) { + var elementIndex = list.elementIndex; + let lastDataBlock = list.blocks[list.blockIndex]; + lastDataBlock[elementIndex] := ?element; elementIndex += 1; - if (elementIndex == last_data_block.size()) { + if (elementIndex == lastDataBlock.size()) { elementIndex := 0; list.blockIndex += 1 }; @@ -459,29 +891,27 @@ module { public func removeLast(list : List) : ?T { var elementIndex = list.elementIndex; if (elementIndex == 0) { - shrink_index_block_if_needed(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) { - list.blocks[blockIndex + 2] := [var] - } - }; + if (blockIndex + 2 < list.blocks.size()) list.blocks[blockIndex + 2] := [var]; + list.blockIndex := blockIndex }; elementIndex -= 1; - var last_data_block = list.blocks[list.blockIndex]; + let 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,16 +979,24 @@ module { /// /// Space: `O(1)` public func get(list : List, index : Nat) : ?T { - let (a, b) = locate(index); + // inlined version of locate + 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`. - /// 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 @@ -570,10 +1008,19 @@ module { /// /// Runtime: `O(1)` public func put(list : List, index : Nat, value : T) { - let (a, b) = locate(index); - 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" + 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))) + }; + + 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`. @@ -595,12 +1042,33 @@ 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 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 } }; @@ -650,8 +1118,52 @@ 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)) + if (isEmpty(list)) return null; + nextIndexOf(list, equal, element, 0) + }; + + /// 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, 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) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + 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); + + let blocks = list.blocks; + let blockCount = blocks.size(); + + var i = blockIndex; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return null; + + var j = if (i == blockIndex) elementIndex else 0; + while (j < sz) { + switch (db[j]) { + case (?x) if (equal(x, element)) return ?indexByBlockElement(i, j); + case null return null + }; + j += 1 + }; + i += 1 + }; + null }; /// Finds the last index of `element` in `list` using equality of elements defined @@ -670,9 +1182,51 @@ 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 { - // inlining would save 10 instructions per entry - findLastIndex(list, func(x) = equal(element, x)) + public func lastIndexOf(list : List, equal : (T, T) -> Bool, element : T) : ?Nat = prevIndexOf( + list, + equal, + element, + size(list) + ); + + /// 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, 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) + /// + /// Space: O(1) + 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; + let (blockIndex, elementIndex) = locate(fromExclusive); + + var i = blockIndex; + if (elementIndex == 0) i -= 1; + + 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 ?indexByBlockElement(i, j); + case null Prim.trap INTERNAL_ERROR + } + }; + i -= 1 + }; + + null }; /// Returns the first value in `list` for which `predicate` returns true. @@ -689,12 +1243,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) = at(list, i)) }; /// Finds the index of the first element in `list` for which `predicate` is true. @@ -716,29 +1265,26 @@ 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 ?indexByBlockElement(i, 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. @@ -760,32 +1306,28 @@ 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()) { - 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 + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = blockIndex; + if (elementIndex == 0) i -= 1; + + 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 ?indexByBlockElement(i, j); + case null Prim.trap INTERNAL_ERROR + } }; - switch (db[elementIndex]) { - case (?x) { - i -= 1; - if (predicate(x)) return ?i - }; - case (_) Prim.trap(INTERNAL_ERROR) - } - } + i -= 1 + }; + + null }; /// Performs binary search on a sorted list to find the index of the `element`. @@ -809,21 +1351,84 @@ 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 } { - var left = 0; - var right = size(list); - while (left < right) { - let mid = (left + right) / 2; - switch (compare(at(list, mid), element)) { - case (#less) left := mid + 1; - case (#greater) right := mid; - case (#equal) return #found mid - } + // 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 + + let blocks = list.blocks; + 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 + }; + + // 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); + switch (compare(x, element)) { + case (#less) {}; + case (#equal) return #found(i); + case (#greater) return #insertionIndex(i) + } + }; + return #insertionIndex(to) + }; + + // binary search the blockIndex in [left, right) + let blockIndex = do { + // 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; + switch (compare(Option.unwrap(blocks[mid][0]), element)) { + case (#less) left := mid; + case (#greater) right := mid; + case (#equal) return #found(indexByBlockElement(mid, 0)) + } + }; + left + }; + + // binary search the elementIndex + 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(indexByBlockElement(blockIndex, mid)) + } + }; + left }; - #insertionIndex left + + #insertionIndex(indexByBlockElement(blockIndex, elementIndex)) }; /// Returns true iff every element in `list` satisfies `predicate`. @@ -845,7 +1450,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`. @@ -866,12 +1490,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 { - switch (findIndex(list, predicate)) { - case (null) false; - case (_) true - } - }; + 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 @@ -895,7 +1514,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) : Types.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 @@ -918,7 +1561,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; @@ -969,7 +1612,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()) { @@ -977,15 +1620,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] @@ -1013,7 +1654,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; @@ -1022,22 +1663,20 @@ 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) { i -= 1; return ?(i, x) }; - case (_) Prim.trap(INTERNAL_ERROR) + case (_) Prim.trap INTERNAL_ERROR } } }; @@ -1061,7 +1700,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. @@ -1079,7 +1718,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 @@ -1104,7 +1743,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) }; @@ -1119,71 +1758,17 @@ 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 { - let blocks = list.blocks.size(); - var blockIndex = 0; - var elementIndex = 0; - var db_size = 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) { - 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) - } - }; + public func toArray(list : List) : [T] { + var blockIndex = 0; + var elementIndex = 0; + var sz = 0; + var db : [var ?T] = [var]; - // 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) { + 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(); elementIndex := 0 }; switch (db[elementIndex]) { @@ -1191,9 +1776,11 @@ module { elementIndex += 1; return x }; - case (_) Prim.trap(INTERNAL_ERROR) + case (_) Prim.trap INTERNAL_ERROR } - } + }; + + Array.tabulate(size(list), generator) }; /// Creates a List containing elements from an Array. @@ -1212,37 +1799,30 @@ 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] { - let block = VarArray.repeat(null, len); - var j = 0; - while (j < fill) { - block[j] := ?array[pos]; - j += 1; - pos += 1 - }; - block - }; - while (i < blockIndex) { - let len = data_block_size(i); - data_blocks[i] := make_block(len, len); + let len = dataBlockSize(i); + dataBlocks[i] := VarArray.tabulate(len, func i = ?array[pos + i]); + pos += len; i += 1 }; if (elementIndex != 0 and blockIndex < blocks) { - data_blocks[i] := make_block(data_block_size(i), elementIndex) + dataBlocks[i] := VarArray.tabulate( + dataBlockSize(i), + func i = if (i < elementIndex) ?array[pos + i] else null + ) }; { - var blocks = data_blocks; + var blocks = dataBlocks; var blockIndex = blockIndex; var elementIndex = elementIndex - }; - + } }; /// Creates a new mutable array containing all elements from the list. @@ -1260,16 +1840,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. @@ -1289,14 +1886,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; @@ -1305,21 +1901,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) + if (elementIndex != 0) { + 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. @@ -1334,7 +1933,7 @@ module { /// /// Space: `O(1)` public func first(list : List) : ?T { - if (isEmpty(list)) null else list.blocks[1][0] + if (list.blockIndex == 1) null else list.blocks[1][0] }; /// Returns the last element of `list`, or `null` if the list is empty. @@ -1379,28 +1978,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 } }; @@ -1426,40 +2021,177 @@ 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; }; + 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) f(index, x); + case null return + }; + j += 1; + index += 1 }; - iterate(list, o.fx); - */ + i += 1 + } + }; + + 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. + /// + /// 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(list)); + /// 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) : Types.Iter = object { + let (start, end) = actualInterval(fromInclusive, toExclusive, size(list)); let blocks = list.blocks.size(); var blockIndex = 0; var elementIndex = 0; - var size = 0; - var db : [var ?T] = [var]; - var i = 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 = fromInclusive; - loop { - if (elementIndex == size) { + 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 + } + }; + + func sliceToArrayBase(list : List, start : Nat) : { + next(i : Nat) : T + } = object { + 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(i : Nat) : T { + if (elementIndex == dbSize) { blockIndex += 1; - if (blockIndex >= blocks) return; db := list.blocks[blockIndex]; - size := db.size(); - if (size == 0) return; + dbSize := db.size(); elementIndex := 0 }; switch (db[elementIndex]) { case (?x) { - f(i, x); elementIndex += 1; - i += 1 + return x }; - case (_) return + case null Prim.trap INTERNAL_ERROR } } }; + /// 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 + /// import VarArray "mo:core/VarArray"; + /// import Nat "mo:core/Nat"; + /// + /// let array = List.fromArray([1, 2, 3, 4, 5]); + /// + /// let slice1 = List.sliceToVarArray(array, 1, 4); + /// assert VarArray.equal(slice1, [var 2, 3, 4], Nat.equal); + /// + /// let slice2 = List.sliceToVarArray(array, 1, -1); + /// assert VarArray.equal(slice2, [var 2, 3, 4], Nat.equal); + /// ``` + /// + /// 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, /// from end to beginning. /// @@ -1482,29 +2214,28 @@ 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); - - loop { - if (blockIndex == 1) { - return - }; - if (elementIndex == 0) { - blockIndex -= 1; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 - } else { - elementIndex -= 1 + var index = 0; + + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = blockIndex; + if (elementIndex == 0) i -= 1; + + 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 } }; @@ -1528,27 +2259,25 @@ 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 (blockIndex == 1) { - return - }; - if (elementIndex == 0) { - blockIndex -= 1; - db := list.blocks[blockIndex]; - elementIndex := db.size() - 1 - } else { - elementIndex -= 1 + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = blockIndex; + if (elementIndex == 0) i -= 1; + + 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 } }; @@ -1596,19 +2325,36 @@ 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; + public func max(list : List, compare : (T, T) -> Types.Order) : ?T { + var maxSoFar : T = switch (first(list)) { + case (?x) x; + case null return null + }; - var maxSoFar = at(list, 0); - forEach( - list, - func(x) = switch (compare(x, maxSoFar)) { - case (#greater) maxSoFar := x; - case _ {} - } - ); + 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 ?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`. @@ -1631,19 +2377,36 @@ 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; + public func min(list : List, compare : (T, T) -> Types.Order) : ?T { + var minSoFar : T = switch (first(list)) { + case (?x) x; + case null return null + }; - var minSoFar = at(list, 0); - forEach( - list, - func(x) = switch (compare(x, minSoFar)) { - case (#less) minSoFar := x; - case _ {} - } - ); + 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 ?minSoFar; - 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. @@ -1668,18 +2431,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 }; @@ -1705,23 +2479,33 @@ 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 { - let size1 = size(list1); - let size2 = size(list2); - let minSize = if (size1 < size2) { size1 } else { size2 }; + 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()); - 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; + label l while (i < blockCount) { + let db1 = blocks1[i]; + let db2 = blocks2[i]; + let sz = Nat.min(db1.size(), db2.size()); + if (sz == 0) break l; + + 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 (_, _) break l + }; + 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 @@ -1742,17 +2526,29 @@ 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 + var text = switch (first(list)) { + case (?x) f(x); + case null "" }; - if (vsize > 0) { - // avoid the trailing comma - text := text # f(at(list, i)) + + let blocks = list.blocks; + let blockCount = blocks.size(); + + 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 break l + }; + j += 1 + }; + i += 1 }; "List[" # text # "]" @@ -1779,11 +2575,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 }; @@ -1808,10 +2618,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) - ); + let blocks = list.blocks; + let blockIndex = list.blockIndex; + let elementIndex = list.elementIndex; + + var i = blockIndex; + if (elementIndex == 0) i -= 1; + + 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 + } + }; + i -= 1 + }; accumulation }; @@ -1834,17 +2660,42 @@ module { /// Space: `O(1)` public func reverseInPlace(list : List) { let vsize = size(list); - if (vsize == 0) return; + if (vsize <= 1) return; - var i = 0; - var j = vsize - 1 : Nat; - var temp = at(list, 0); - while (i < vsize / 2) { - temp := at(list, j); - put(list, j, at(list, i)); - put(list, i, temp); - i += 1; - j -= 1 + let (finalBlock, finalElement) = locate(vsize / 2); + + let blocks = list.blocks; + + 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 <= finalBlock) { + let db = blocks[i]; + let sz = if (i == finalBlock) finalElement else db.size(); + + var j = 0; + while (j < sz) { + 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 } }; @@ -1865,13 +2716,39 @@ 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; + let blockCount = blocks.size(); + + var blockIndexBack = rlist.blockIndex; + var elementIndexBack = rlist.elementIndex; + var dbBack : [var ?T] = if (blockIndexBack < rlist.blocks.size()) { + rlist.blocks[blockIndexBack] + } else { [var] }; + + var i = 1; + while (i < blockCount) { + let db = blocks[i]; + let sz = db.size(); + if (sz == 0) return rlist; + + 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 + }; + dbBack[elementIndexBack] := db[j]; + j += 1 + }; + i += 1 + }; rlist }; @@ -1888,6 +2765,7 @@ module { /// /// Space: `O(1)` public func isEmpty(list : List) : Bool { - list.blockIndex == 1 and list.elementIndex == 0 - } + list.blockIndex == 1 + }; + } diff --git a/test/List.test.mo b/test/List.test.mo index f5bc7303..b055a0d0 100644 --- a/test/List.test.mo +++ b/test/List.test.mo @@ -14,6 +14,158 @@ import Runtime "../src/Runtime"; 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"; + +// 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; + 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 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) { + 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.any(db, Option.isNull)) { + nullCount += 1; + assert i == list.blockIndex or i == list.blockIndex + 1 + }; + i += 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; @@ -818,90 +970,80 @@ run( ) ); -/* --------------------------------------- */ +func joinWith(xs : List.List, sep : Text) : Text { + let size = List.size(xs); -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) + if (size == 0) return ""; + if (size == 1) return List.at(xs, 0); + + var result = List.at(xs, 0); + var i = 0; + label l loop { + i += 1; + if (i >= size) { break l }; + result #= sep # List.at(xs, i) }; - 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)) + result }; -// 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)) +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) } }; -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 -}; +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([])))) + ) + ] + ) +); // Claude tests (from original Mops package) // 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)) @@ -913,20 +1055,35 @@ 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); - List.size(vec) == n and (n == 0 or (List.at(vec, 0) == 1 and List.at(vec, n - 1 : Nat) == 1)) + assertValid(vec); + 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.at(vec, i) != 1) { + Debug.print("Init failed at index " # Nat.toText(i) # ": expected 1, got " # Nat.toText(List.at(vec, i))); + return false + } + }; + true }; 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) { @@ -936,6 +1093,7 @@ func testAdd(n : Nat) : Bool { for (i in Nat.range(0, n)) { let value = List.at(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 @@ -945,31 +1103,43 @@ 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.at(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 + 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 + }; + for (k in Nat.range(0, i + j)) { + let expected = if (k < i) 0 else 1; + let got = List.at(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 }; 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 @@ -996,6 +1166,7 @@ func testRemoveLast(n : Nat) : Bool { func testAt(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.at(vec, i - 1 : Nat); @@ -1009,10 +1180,10 @@ func testAt(n : Nat) : Bool { }; func testGet(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.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)); @@ -1026,14 +1197,13 @@ func testGet(n : Nat) : Bool { } }; - // Test out-of-bounds access - switch (List.get(vec, n)) { - case (null) { - // This is expected - }; - case (?value) { - Debug.print("get: Expected null for out-of-bounds access, got ?" # Nat.toText(value)); - return false + for (i in Nat.range(n, 3 * n + 3)) { + switch (List.get(vec, i)) { + case (?value) { + Debug.print("get: Unexpected value at index " # Nat.toText(i) # ": got ?" # Nat.toText(value)); + return false + }; + case (null) {} } }; @@ -1041,33 +1211,110 @@ func testGet(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.at(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.at(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 { let vec = List.fromArray(Array.tabulate(n, func(i) = i)); List.clear(vec); + assertValid(vec); List.size(vec) == 0 }; func testClone(n : Nat) : Bool { + if (n == 0) { + 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); + assertValid(vec2); List.equal(vec1, vec2, Nat.equal) }; 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); + assertValid(mapped); 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 sliceVar = List.sliceToVarArray(vec, left, right); + let expected = Array.tabulate(right - left, func(i) = left + i); + 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) + ); + return false + } + } + }; + true +}; + func testIndexOf(n : Nat) : Bool { let vec = List.fromArray(Array.tabulate(2 * n, func(i) = i % n)); if (n == 0) { @@ -1127,10 +1374,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); - List.equal(vec, List.fromArray(Array.tabulate(n, func(i) = n - 1 - i)), Nat.equal) + 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); + + inPlaceEqual and reversedEqual }; func testSort(n : Nat) : Bool { @@ -1146,13 +1402,34 @@ 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); + assertValid(vec); + 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 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) }; @@ -1170,6 +1447,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"); @@ -1177,12 +1455,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 @@ -1195,6 +1475,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"); @@ -1202,12 +1483,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 @@ -1216,12 +1499,265 @@ 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); + assertValid(newVec); + + 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 +}; + +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 +}; + +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) +}; + +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.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 + } + } + }; + + 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.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 + } + } + }; + + 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.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 + } + }; + + 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.at(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, Nat.equal, element, from); + 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.at(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 + 1)) { + for (element in Nat.range(0, n + 1)) { + let actual = List.prevIndexOf(vec, Nat.equal, element, from); + 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 +}; + +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); runTest("testInit", testInit); runTest("testAdd", testAdd); - runTest("testAddAll", testAddAll); + runTest("testAddRepeat", testAddRepeat); runTest("testRemoveLast", testRemoveLast); runTest("testAt", testAt); runTest("testGet", testGet); @@ -1229,17 +1765,36 @@ 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); runTest("testReverse", testReverse); runTest("testSort", testSort); runTest("testToArray", testToArray); + runTest("testToVarArray", testToVarArray); + runTest("testFromVarArray", testFromVarArray); + runTest("testFromArray", testFromArray); runTest("testFromIter", testFromIter); runTest("testFoldLeft", testFoldLeft); runTest("testFoldRight", testFoldRight); runTest("testFilter", testFilter); - runTest("testFilterMap", testFilterMap) + runTest("testFilterMap", testFilterMap); + runTest("testPure", testPure); + runTest("testReverseForEach", testReverseForEach); + runTest("testForEach", testForEach); + runTest("testBinarySearch", testBinarySearch); + runTest("testFlatten", testFlatten); + runTest("testJoin", testJoin); + runTest("testTabulate", testTabulate); + runTest("testNextIndexOf", testNextIndexOf); + runTest("testPrevIndexOf", testPrevIndexOf); + runTest("testMin", testMin); + runTest("testMax", testMax) }; // Run all tests diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index f3a94cbf..a5097240 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,42 +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 -> 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 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 max(list : List, compare : (T, T) -> Order.Order) : ?T", - "public func min(list : List, compare : (T, T) -> Order.Order) : ?T", + "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 -> 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) : 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 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", "public func toVarArray(list : List) : [var T]", - "public func values(list : List) : Iter.Iter" + "public func values(list : List) : Types.Iter" ] }, {