Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
## Next

* **Breaking:** Rename `sort` to `sortInPlace`, add `sort` (#405).
* Improve CI for external contributions (#413).
* Add `forEachRange` to `List` (#411).
* Add `isSorted` to `List` (#408).
* **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).
Expand Down
50 changes: 50 additions & 0 deletions src/List.mo
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,56 @@ module {
}
};

/// Executes the closure over a slice of `list` starting at `fromInclusive` up to (but not including) `toExclusive`.
///
/// ```motoko include=import
/// import Debug "mo:core/Debug";
/// import Nat "mo:core/Nat";
///
/// let list = List.fromArray<Nat>([1, 2, 3, 4, 5]);
/// List.forEachRange<Nat>(list, func x = Debug.print(Nat.toText(x)), 1, 2); // prints 2 and 3
/// ```
///
/// Runtime: `O(toExclusive - fromExclusive)`
///
/// Space: `O(1)`
public func forEachRange<T>(list : List<T>, f : T -> (), fromInclusive : Nat, toExclusive : Nat) {
if (not (fromInclusive <= toExclusive and toExclusive <= size(list))) Prim.trap("Invalid range");

func traverseBlock(block : [var ?T], f : T -> (), from : Nat, to : Nat) {
var i = from;
while (i < to) {
switch (block[i]) {
case (?value) f(value);
case null Prim.trap(INTERNAL_ERROR)
};
i += 1
}
};

let (fromBlock, fromElement) = locate(fromInclusive);
let (toBlock, toElement) = locate(toExclusive);

let blocks = list.blocks;
let sz = blocks.size();

if (fromBlock == toBlock) {
if (fromBlock < sz) traverseBlock(blocks[fromBlock], f, fromElement, toElement);
return
};

traverseBlock(blocks[fromBlock], f, fromElement, blocks[fromBlock].size());

var i = fromBlock + 1;
let to = Nat.min(toBlock, sz);
while (i < to) {
traverseBlock(blocks[i], f, 0, blocks[i].size());
i += 1
};

if (toBlock < sz) traverseBlock(blocks[toBlock], f, 0, toElement)
};

/// Returns true if the list contains the specified element according to the provided
/// equality function. Uses the provided `equal` function to compare elements.
///
Expand Down
22 changes: 22 additions & 0 deletions test/List.test.mo
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Runtime "../src/Runtime";
import Int "../src/Int";
import Debug "../src/Debug";
import { Tuple2 } "../src/Tuples";
import VarArray "../src/VarArray";

let { run; test; suite } = Suite;

Expand Down Expand Up @@ -1172,6 +1173,26 @@ func testFromIter(n : Nat) : Bool {
List.equal(vec, List.fromArray<Nat>(Array.tabulate<Nat>(n, func(i) = i + 1)), Nat.equal)
};

func testForEachRange(n : Nat) : Bool {
if (n > 10) return true; // Skip large ranges for performance
let vec = List.fromArray<Nat>(Array.tabulate<Nat>(n, func(i) = i));

for (left in Nat.range(0, n)) {
for (right in Nat.range(left, n + 1)) {
let expected = VarArray.tabulate<Nat>(right - left, func(i) = left + i);
let result = VarArray.repeat<Nat>(0, right - left);
List.forEachRange<Nat>(vec, func(i) = result[i - left] := i, left, right);
if (Array.fromVarArray(result) != Array.fromVarArray(expected)) {
Debug.print(
"ForEachRange mismatch for left = " # Nat.toText(left) # ", right = " # Nat.toText(right) # ": expected " # debug_show (expected) # ", got " # debug_show (result)
);
return false
}
}
};
true
};

func testFoldLeft(n : Nat) : Bool {
let vec = List.fromArray<Nat>(Array.tabulate<Nat>(n, func(i) = i + 1));
List.foldLeft<Text, Nat>(vec, "", func(acc, x) = acc # Nat.toText(x)) == Array.foldLeft<Nat, Text>(Array.tabulate<Nat>(n, func(i) = i + 1), "", func(acc, x) = acc # Nat.toText(x))
Expand Down Expand Up @@ -1255,6 +1276,7 @@ func runAllTests() {
runTest("testFromIter", testFromIter);
runTest("testFoldLeft", testFoldLeft);
runTest("testFoldRight", testFoldRight);
runTest("testForEachRange", testForEachRange);
runTest("testFilter", testFilter);
runTest("testFilterMap", testFilterMap)
};
Expand Down
1 change: 1 addition & 0 deletions validation/api/api.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@
"public func foldRight<T, A>(list : List<T>, base : A, combine : (T, A) -> A) : A",
"public func forEach<T>(list : List<T>, f : T -> ())",
"public func forEachEntry<T>(list : List<T>, f : (Nat, T) -> ())",
"public func forEachRange<T>(list : List<T>, f : T -> (), fromInclusive : Nat, toExclusive : Nat)",
"public func fromArray<T>(array : [T]) : List<T>",
"public func fromIter<T>(iter : Iter.Iter<T>) : List<T>",
"public func fromPure<T>(pure : PureList.List<T>) : List<T>",
Expand Down