diff --git a/src/Array.mo b/src/Array.mo index 2d72ec91..f3d16482 100644 --- a/src/Array.mo +++ b/src/Array.mo @@ -21,25 +21,27 @@ import { todo } "Debug"; module { - /// Create an empty array (equivalent to `[]`). + /// Creates an empty array (equivalent to `[]`). public func empty() : [T] = []; - /// Create an array with `size` copies of the initial value. + /// Creates an array containing `item` repeated `size` times. /// /// ```motoko include=import - /// let array = Array.init(4, 2); + /// let array = Array.repeat("Echo", 3); + /// assert array == [var "Echo", "Echo", "Echo"]; /// ``` /// /// Runtime: O(size) /// /// Space: O(size) - public func init(size : Nat, initValue : T) : [T] = Prim.Array_tabulate(size, func _ = initValue); + public func repeat(item : T, size : Nat) : [T] = Prim.Array_tabulate(size, func _ = item); - /// Create an immutable array of size `size`. Each element at index i + /// Creates an immutable array of size `size`. Each element at index i /// is created by applying `generator` to i. /// /// ```motoko include=import - /// let array : [Nat] = Array.generate(4, func i = i * 2); + /// let array : [Nat] = Array.tabulate(4, func i = i * 2); + /// assert array == [0, 2, 4, 6]; /// ``` /// /// Runtime: O(size) @@ -47,7 +49,7 @@ module { /// Space: O(size) /// /// *Runtime and space assumes that `generator` runs in O(1) time and space. - public func generate(size : Nat, generator : Nat -> T) : [T] = Prim.Array_tabulate(size, generator); + public func tabulate(size : Nat, generator : Nat -> T) : [T] = Prim.Array_tabulate(size, generator); /// Transforms a mutable array into an immutable array. /// @@ -213,7 +215,7 @@ module { /// import Debug "mo:base/Debug"; /// /// let array = [0, 1, 2, 3]; - /// Array.forEach(array, func (x) { + /// Array.forEach(array, func(x) { /// Debug.print(debug_show x) /// }) /// ``` @@ -235,8 +237,9 @@ module { /// /// ```motoko include=import /// - /// let array = [0, 1, 2, 3]; - /// Array.map(array, func x = x * 3) + /// let array1 = [0, 1, 2, 3]; + /// let array2 = Array.map(array1, func x = x * 3) + /// assert array2 == [0, 2, 4, 6]; /// ``` /// /// Runtime: O(size) @@ -244,7 +247,7 @@ module { /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func map(array : [T], f : T -> Y) : [Y] = Prim.Array_tabulate(array.size(), func i = f(array[i])); + public func map(array : [T], f : T -> R) : [R] = Prim.Array_tabulate(array.size(), func i = f(array[i])); /// Creates a new array by applying `predicate` to every element /// in `array`, retaining the elements for which `predicate` returns true. @@ -300,9 +303,9 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func filterMap(array : [T], f : T -> ?Y) : [Y] { + public func filterMap(array : [T], f : T -> ?R) : [R] { var count = 0; - let options = Prim.Array_tabulate( + let options = Prim.Array_tabulate( array.size(), func i { let result = f(array[i]); @@ -319,7 +322,7 @@ module { ); var nextSome = 0; - Prim.Array_tabulate( + Prim.Array_tabulate( count, func _ { while (Option.isNull(options[nextSome])) { @@ -329,7 +332,7 @@ module { switch (options[nextSome - 1]) { case (?element) element; case null { - Prim.trap "Malformed array in mapFilter" + Prim.trap "Malformed array in filterMap" } } } @@ -357,11 +360,11 @@ module { /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func mapResult(array : [T], f : T -> Result.Result) : Result.Result<[Y], E> { + public func mapResult(array : [T], f : T -> Result.Result) : Result.Result<[R], E> { let size = array.size(); - var error : ?Result.Result<[Y], E> = null; - let results = Prim.Array_tabulate( + var error : ?Result.Result<[R], E> = null; + let results = Prim.Array_tabulate( size, func i { switch (f(array[i])) { @@ -386,7 +389,7 @@ module { case null { // unpack the option #ok( - map( + map( results, func element { switch element { @@ -437,12 +440,12 @@ module { /// /// Space: O(size) /// *Runtime and space assumes that `k` runs in O(1) time and space. - public func flatMap(array : [T], k : T -> [R]) : [R] { + public func flatMap(array : [T], k : T -> Iter.Iter) : [R] { var flatSize = 0; let arrays = Prim.Array_tabulate<[R]>( array.size(), func i { - let subArray = k(array[i]); + let subArray = fromIter(k(array[i])); flatSize += subArray.size(); subArray } @@ -522,26 +525,65 @@ module { acc }; - /// Flattens the array of arrays into a single array. Retains the original + /// Combines an iterator of arrays into a single array. Retains the original + /// ordering of the elements. + /// + /// Consider using `Array.flatten()` where possible for better performance. + /// + /// ```motoko include=import + /// + /// let arrays = [[0, 1, 2], [2, 3], [], [4]]; + /// Array.join(Array.fromIter(arrays))) // => [0, 1, 2, 2, 3, 4] + /// ``` + /// + /// Runtime: O(number of elements in array) + /// + /// Space: O(number of elements in array) + public func join(arrays : Iter.Iter<[T]>) : [T] { + flatten(fromIter(arrays)) + }; + + /// Combines an array of arrays into a single array. Retains the original /// ordering of the elements. /// + /// This has better performance compared to `Array.join()`. + /// /// ```motoko include=import /// /// let arrays = [[0, 1, 2], [2, 3], [], [4]]; - /// Array.flatten(arrays) + /// Array.flatten(arrays)) // => [0, 1, 2, 2, 3, 4] /// ``` /// /// Runtime: O(number of elements in array) /// /// Space: O(number of elements in array) - public func flatten(arrays : Iter.Iter<[T]>) : [T] { - todo() // New implementation due to using `Iter<[T]>` in place of `[[T]]` + public func flatten(arrays : [[T]]) : [T] { + var flatSize = 0; + for (subArray in arrays.vals()) { + flatSize += subArray.size() + }; + + var outer = 0; + var inner = 0; + Prim.Array_tabulate( + flatSize, + func _ { + while (inner == arrays[outer].size()) { + inner := 0; + outer += 1 + }; + let element = arrays[outer][inner]; + inner += 1; + element + } + ) }; /// Create an array containing a single value. /// /// ```motoko include=import - /// Array.singleton(2) + /// var array = Array.singleton(2); + /// assert array == [2]; /// ``` /// /// Runtime: O(1) @@ -549,23 +591,25 @@ module { /// Space: O(1) public func singleton(element : T) : [T] = [element]; + /// Returns the size of an array. Equivalent to `array.size()`. public func size(array : [T]) : Nat = array.size(); + /// Returns whether an array is empty, i.e. contains zero elements. public func isEmpty(array : [T]) : Bool = array.size() == 0; + /// Converts an iterator to an array. public func fromIter(iter : Iter.Iter) : [T] { - todo() + todo() // Pending `List` data structure }; - /// Returns an Iterator (`Iter`) over the indices of `array`. - /// Iterator provides a single method `next()`, which returns + /// Returns an iterator (`Iter`) over the indices of `array`. + /// An iterator provides a single method `next()`, which returns /// indices in order, or `null` when out of index to iterate over. /// - /// NOTE: You can also use `array.keys()` instead of this function. See example + /// Note: You can also use `array.keys()` instead of this function. See example /// below. /// /// ```motoko include=import - /// /// let array = [10, 11, 12]; /// /// var sum = 0; @@ -583,11 +627,10 @@ module { /// Iterator provides a single method `next()`, which returns /// elements in order, or `null` when out of elements to iterate over. /// - /// NOTE: You can also use `array.values()` instead of this function. See example + /// Note: You can also use `array.values()` instead of this function. See example /// below. /// /// ```motoko include=import - /// /// let array = [10, 11, 12]; /// /// var sum = 0; @@ -602,28 +645,91 @@ module { /// Space: O(1) public func values(array : [T]) : Iter.Iter = array.vals(); + /// Iterator provides a single method `next()`, which returns + /// pairs of (index, element) in order, or `null` when out of elements to iterate over. + /// + /// ```motoko include=import + /// let array = [10, 11, 12]; + /// + /// var sum = 0; + /// for ((index, element) in Array.enumerate(array)) { + /// sum += element; + /// }; + /// sum // => 33 + /// ``` + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func enumerate(array : [var T]) : Iter.Iter<(Nat, T)> = object { + let size = array.size(); + var index = 0; + public func next() : ?(Nat, T) { + if (index > size) { + return null + }; + let i = index; + index += 1; + ?(i, array[i]) + } + }; + + /// Returns true if all elements in `array` satisfy the predicate function. + /// + /// ```motoko include=import + /// let array = [1, 2, 3, 4]; + /// Array.all(array, func x = x > 0) // => true + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func all(array : [T], predicate : T -> Bool) : Bool { - todo() + for (element in array.vals()) { + if (not predicate(element)) { + return false + } + }; + true }; + /// Returns true if any element in `array` satisfies the predicate function. + /// + /// ```motoko include=import + /// let array = [1, 2, 3, 4]; + /// Array.any(array, func x = x > 3) // => true + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func any(array : [T], predicate : T -> Bool) : Bool { - todo() + for (element in array.vals()) { + if (predicate(element)) { + return true + } + }; + false }; - /// Returns a new subarray from the given array provided the start index and length of elements in the subarray + /// Returns a new sub-array from the given array provided the start index and length of elements in the sub-array. /// /// Limitations: Traps if the start index + length is greater than the size of the array /// /// ```motoko include=import /// - /// let array = [1,2,3,4,5]; + /// let array = [1, 2, 3, 4, 5]; /// let subArray = Array.subArray(array, 2, 3); /// ``` /// Runtime: O(length) /// /// Space: O(length) public func subArray(array : [T], start : Nat, length : Nat) : [T] { - if (start + length > array.size()) { Prim.trap("Array.subArray") }; + if (start + length > array.size()) { Prim.trap("Array.subArray()") }; Prim.Array_tabulate(length, func i = array[start + i]) }; @@ -658,13 +764,13 @@ module { /// /// Space: O(1) public func nextIndexOf(element : T, array : [T], fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat { - var i = fromInclusive; - let n = array.size(); - while (i < n) { - if (equal(array[i], element)) { - return ?i + var index = fromInclusive; + let size = array.size(); + while (index < size) { + if (equal(array[index], element)) { + return ?index } else { - i += 1 + index += 1 } }; null @@ -714,42 +820,117 @@ module { /// /// ```motoko include=import /// let array = [1, 2, 3, 4, 5]; - /// let s = Array.slice(array, 3, array.size()); + /// let s = Array.range(array, 3, array.size()); /// assert s.next() == ?4; /// assert s.next() == ?5; /// assert s.next() == null; /// - /// let s = Array.slice(array, 0, 0); + /// let s = Array.range(array, 0, 0); /// assert s.next() == null; /// ``` /// /// Runtime: O(1) /// /// Space: O(1) - public func slice(array : [T], fromInclusive : Int, toExclusive : Int) : Iter.Iter { - todo() // New implementation due to accepting integer range + public func range(array : [T], fromInclusive : Int, toExclusive : Int) : Iter.Iter { + let size = array.size(); + // Convert negative indices to positive and handle bounds + 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 } + }; + // Convert to Nat (values are non-negative due to bounds checking above) + let start = Prim.abs(startInt); + let end = Prim.abs(endInt); + object { + var pos = start; + public func next() : ?T { + if (pos >= end) { + null + } else { + let elem = array[pos]; + pos += 1; + ?elem + } + } + } }; - /// Returns a new subarray of given length from the beginning or end of the given array - /// - /// Returns the entire array if the length is greater than the size of the array + /// Converts the array to its textual representation using `f` to convert each element to `Text`. /// /// ```motoko include=import - /// let array = [1, 2, 3, 4, 5]; - /// assert Array.take(array, 2) == [1, 2]; - /// assert Array.take(array, -2) == [4, 5]; - /// assert Array.take(array, 10) == [1, 2, 3, 4, 5]; - /// assert Array.take(array, -99) == [1, 2, 3, 4, 5]; + /// import Nat "mo:base/Nat"; + /// let array = [1, 2, 3]; + /// Array.toText(array, Nat.toText) // => "[1, 2, 3]" /// ``` - /// Runtime: O(length) /// - /// Space: O(length) + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. public func toText(array : [T], f : T -> Text) : Text { - todo() + let size = array.size(); + if (size == 0) { return "[]" }; + var text = "["; + var i = 0; + while (i < size) { + if (i != 0) { + text #= ", " + }; + text #= f(array[i]); + i += 1 + }; + text #= "]"; + text }; + /// Compares two arrays using the provided comparison function for elements. + /// Returns #less, #equal, or #greater if `array1` is less than, equal to, + /// or greater than `array2` respectively. + /// + /// If arrays have different sizes but all elements up to the shorter length are equal, + /// the shorter array is considered #less than the longer array. + /// + /// ```motoko include=import + /// import Nat "mo:base/Nat"; + /// let array1 = [1, 2, 3]; + /// let array2 = [1, 2, 4]; + /// Array.compare(array1, array2, Nat.compare) // => #less + /// + /// let array3 = [1, 2]; + /// let array4 = [1, 2, 3]; + /// Array.compare(array3, array4, Nat.compare) // => #less (shorter array) + /// ``` + /// + /// Runtime: O(min(size1, size2)) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func compare(array1 : [T], array2 : [T], compare : (T, T) -> Order.Order) : Order.Order { - todo() + let size1 = array1.size(); + let size2 = array2.size(); + var i = 0; + let minSize = if (size1 < size2) { size1 } else { size2 }; + while (i < minSize) { + switch (compare(array1[i], array2[i])) { + case (#less) { return #less }; + case (#greater) { return #greater }; + case (#equal) { i += 1 } + } + }; + if (size1 < size2) { #less } else if (size1 > size2) { #greater } else { + #equal + } }; } diff --git a/src/Int.mo b/src/Int.mo index af70786f..d819f73d 100644 --- a/src/Int.mo +++ b/src/Int.mo @@ -412,12 +412,70 @@ module { /// as a function value at the moment. public func pow(x : Int, y : Int) : Int { x ** y }; + /// Returns an iterator over the integers from the first to second argument with an exclusive upper bound. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Int, toExclusive : Int) : Iter.Iter { - todo() + object { + var n = fromInclusive; + public func next() : ?Int { + if (n >= toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } }; + /// Returns an iterator over the integers from the first to second argument, inclusive. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int.rangeInclusive(3, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Int, to : Int) : Iter.Iter { - todo() + object { + var n = from; + public func next() : ?Int { + if (n > to) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } }; } diff --git a/src/Int16.mo b/src/Int16.mo index 4c261bcf..03f30f4b 100644 --- a/src/Int16.mo +++ b/src/Int16.mo @@ -642,16 +642,97 @@ module { /// as a function value at the moment. public func powWrap(x : Int16, y : Int16) : Int16 { x **% y }; + /// Returns an iterator over `Int16` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int16.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int16.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Int16, toExclusive : Int16) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Int16 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Int16` values from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int16.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int16.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Int16, to : Int16) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Int16 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Int16 values, from minValue to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int16.allValues(); + /// assert(?-32_768 == iter.next()); + /// assert(?-32_767 == iter.next()); + /// assert(?-32_766 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(minValue, maxValue) }; } diff --git a/src/Int32.mo b/src/Int32.mo index ce0cb35f..af116994 100644 --- a/src/Int32.mo +++ b/src/Int32.mo @@ -652,16 +652,97 @@ module { /// as a function value at the moment. public func powWrap(x : Int32, y : Int32) : Int32 { x **% y }; + /// Returns an iterator over `Int32` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int32.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int32.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Int32, toExclusive : Int32) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Int32 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Int32` values from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int32.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int32.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Int32, to : Int32) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Int32 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Int32 values, from minValue to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int32.allValues(); + /// assert(?-2_147_483_648 == iter.next()); + /// assert(?-2_147_483_647 == iter.next()); + /// assert(?-2_147_483_646 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(minValue, maxValue) }; } diff --git a/src/Int64.mo b/src/Int64.mo index 914308cf..ff227e22 100644 --- a/src/Int64.mo +++ b/src/Int64.mo @@ -639,16 +639,97 @@ module { /// as a function value at the moment. public func powWrap(x : Int64, y : Int64) : Int64 { x **% y }; + /// Returns an iterator over `Int64` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int64.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int64.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Int64, toExclusive : Int64) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Int64 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Int64` values from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int64.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int64.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Int64, to : Int64) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Int64 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Int64 values, from minValue to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int64.allValues(); + /// assert(?-9_223_372_036_854_775_808 == iter.next()); + /// assert(?-9_223_372_036_854_775_807 == iter.next()); + /// assert(?-9_223_372_036_854_775_806 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(minValue, maxValue) }; } diff --git a/src/Int8.mo b/src/Int8.mo index fc85f2a6..173b985b 100644 --- a/src/Int8.mo +++ b/src/Int8.mo @@ -633,16 +633,97 @@ module { /// as a function value at the moment. public func powWrap(x : Int8, y : Int8) : Int8 { x **% y }; + /// Returns an iterator over `Int8` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int8.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int8.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Int8, toExclusive : Int8) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Int8 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Int8` values from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int8.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int8.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Int8, to : Int8) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Int8 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Int8 values, from minValue to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Int8.allValues(); + /// assert(?-128 == iter.next()); + /// assert(?-127 == iter.next()); + /// assert(?-126 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(minValue, maxValue) }; } diff --git a/src/Iter.mo b/src/Iter.mo index 790f42f2..b4f16265 100644 --- a/src/Iter.mo +++ b/src/Iter.mo @@ -24,6 +24,14 @@ module { /// ``` public type Iter = { next : () -> ?T }; + public func empty() : Iter { + object { + public func next() : ?T { + null + } + } + }; + /// Calls a function `f` on every value produced by an iterator and discards /// the results. If you're looking to keep these results use `map` instead. /// diff --git a/src/List.mo b/src/List.mo index 8424d022..2d1bfce7 100644 --- a/src/List.mo +++ b/src/List.mo @@ -204,7 +204,11 @@ module { todo() }; - public func flatten(lists : Iter.Iter>) : List { + public func join(lists : Iter.Iter>) : List { + todo() + }; + + public func flatten(lists : List>) : List { todo() }; diff --git a/src/Map.mo b/src/Map.mo index a4f268d6..3b17931e 100644 --- a/src/Map.mo +++ b/src/Map.mo @@ -160,7 +160,7 @@ module { { var root = #leaf({ data = { - kvs = VarArray.generate(btreeOrder - 1, func(index) { null }); + kvs = VarArray.tabulate(btreeOrder - 1, func(index) { null }); var count = 0 } }); @@ -187,7 +187,7 @@ module { { var root = #leaf({ data = { - kvs = VarArray.generate( + kvs = VarArray.tabulate( btreeOrder - 1, func(index) { if (index == 0) { @@ -461,7 +461,7 @@ module { case (#promote({ kv; leftChild; rightChild })) { map.root := #internal({ data = { - kvs = VarArray.generate( + kvs = VarArray.tabulate( btreeOrder - 1, func(i) { if (i == 0) { ?kv } else { null } @@ -469,7 +469,7 @@ module { ); var count = 1 }; - children = VarArray.generate)>( + children = VarArray.tabulate)>( btreeOrder, func(i) { if (i == 0) { ?leftChild } else if (i == 1) { ?rightChild } else { @@ -2150,7 +2150,7 @@ module { if (splitIndex > array.size()) { assert false }; let leftSplit = if (insertIndex < splitIndex) { - VarArray.generate( + VarArray.tabulate( array.size(), func(i) { // if below the split index @@ -2167,7 +2167,7 @@ module { } // index >= splitIndex else { - VarArray.generate( + VarArray.tabulate( array.size(), func(i) { // right biased splitting @@ -2179,7 +2179,7 @@ module { let (rightSplit, middleElement) : ([var ?T], ?T) = // if insert > split index, inserted element will be inserted into the right split if (insertIndex > splitIndex) { - let right = VarArray.generate( + let right = VarArray.tabulate( array.size(), func(i) { let adjIndex = i + splitIndex + 1; // + 1 accounts for the fact that the split element was part of the original array @@ -2194,7 +2194,7 @@ module { } // if inserted element was placed in the left split else if (insertIndex < splitIndex) { - let right = VarArray.generate( + let right = VarArray.tabulate( array.size(), func(i) { let adjIndex = i + splitIndex; @@ -2205,7 +2205,7 @@ module { } // insertIndex == splitIndex else { - let right = VarArray.generate( + let right = VarArray.tabulate( array.size(), func(i) { let adjIndex = i + splitIndex; @@ -2243,7 +2243,7 @@ module { public func splitArrayAndInsertTwo(children : [var ?T], rebalancedChildIndex : Nat, leftChildInsert : T, rightChildInsert : T) : ([var ?T], [var ?T]) { let splitIndex = children.size() / 2; - let leftRebalancedChildren = VarArray.generate( + let leftRebalancedChildren = VarArray.tabulate( children.size(), func(i) { // only insert elements up to the split index and fill the rest of the children with nulls @@ -2262,7 +2262,7 @@ module { let rightRebalanceChildren : [var ?T] = // Case 1: if both left and right rebalanced halves were inserted into the left child can just go from the split index onwards if (rebalancedChildIndex + 1 <= splitIndex) { - VarArray.generate( + VarArray.tabulate( children.size(), func(i) { let adjIndex = i + splitIndex; @@ -2273,7 +2273,7 @@ module { // Case 2: if both left and right rebalanced halves will be inserted into the right child else if (rebalancedChildIndex > splitIndex) { var rebalanceOffset = 0; - VarArray.generate( + VarArray.tabulate( children.size(), func(i) { let adjIndex = i + splitIndex + 1; @@ -2289,7 +2289,7 @@ module { // Case 3: if left rebalanced half was in left child, and right rebalanced half will be in right child // rebalancedChildIndex == splitIndex else { - VarArray.generate( + VarArray.tabulate( children.size(), func(i) { // first element is the right rebalanced half @@ -2431,7 +2431,7 @@ module { deleteIndex : Nat, deletionSide : DeletionSide ) : ([var ?T], T) { - let mergedArray = VarArray.init(leftChild.size(), null); + let mergedArray = VarArray.repeat(null, leftChild.size()); var i = 0; switch (deletionSide) { case (#left) { @@ -2789,7 +2789,7 @@ module { assert leftData.count <= minKeysFromOrder(leftData.kvs.size() + 1); assert rightData.count <= minKeysFromOrder(rightData.kvs.size() + 1); - let mergedKVs = VarArray.init(leftData.kvs.size(), null); + let mergedKVs = VarArray.repeat(null, leftData.kvs.size()); var i = 0; while (i < leftData.count) { mergedKVs[i] := leftData.kvs[i]; @@ -2813,7 +2813,7 @@ module { }; func mergeChildren(leftChildren : [var ?Node], rightChildren : [var ?Node]) : [var ?Node] { - let mergedChildren = VarArray.init>(leftChildren.size(), null); + let mergedChildren = VarArray.repeat>(null, leftChildren.size()); var i = 0; while (Option.isSome(leftChildren[i])) { diff --git a/src/Nat.mo b/src/Nat.mo index 50c4eb50..10c96a2f 100644 --- a/src/Nat.mo +++ b/src/Nat.mo @@ -356,26 +356,83 @@ module { /// rule. public func bitshiftRight(x : Nat, y : Nat32) : Nat { Prim.shiftRight(x, y) }; - public func range(fromInclusive : Nat, toExclusive : Nat) : Iter.Iter { - var number = fromInclusive; - object { - public func next() : ?Nat { - if (number >= toExclusive) { - return null - }; - let current = number; - number += 1; - ?current - } + /// Returns an iterator over `Nat` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` + public func range(fromInclusive : Nat, toExclusive : Nat) : Iter.Iter = object { + var n = fromInclusive; + public func next() : ?Nat { + if (n >= toExclusive) { + return null + }; + let current = n; + n += 1; + ?current } }; - public func rangeInclusive(from : Nat, to : Nat) : Iter.Iter { - todo() + /// Returns an iterator over the integers from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat.rangeInclusive(3, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` + public func rangeInclusive(from : Nat, to : Nat) : Iter.Iter = object { + var n = from; + public func next() : ?Nat { + if (n > to) { + return null + }; + let current = n; + n += 1; + ?current + } }; - public func allValues() : Iter.Iter { - todo() + /// Returns an infinite iterator over all possible `Nat` values. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat.allValues(); + /// assert(?0 == iter.next()); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// // ... + /// ``` + public func allValues() : Iter.Iter = object { + var n = 0; + public func next() : ?Nat { + let current = n; + n += 1; + ?current + } }; } diff --git a/src/Nat16.mo b/src/Nat16.mo index 29c194ae..bec5888c 100644 --- a/src/Nat16.mo +++ b/src/Nat16.mo @@ -576,16 +576,97 @@ module { /// as a function value at the moment. public func powWrap(x : Nat16, y : Nat16) : Nat16 { x **% y }; + /// Returns an iterator over `Nat16` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat16.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat16.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Nat16, toExclusive : Nat16) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Nat16 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Nat16` values from the first to second argument, inclusive. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat16.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat16.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Nat16, to : Nat16) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Nat16 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Nat16 values, from 0 to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat16.allValues(); + /// assert(?0 == iter.next()); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(0, maxValue) }; } diff --git a/src/Nat32.mo b/src/Nat32.mo index 720f1c14..6cb986c6 100644 --- a/src/Nat32.mo +++ b/src/Nat32.mo @@ -585,16 +585,97 @@ module { /// as a function value at the moment. public func powWrap(x : Nat32, y : Nat32) : Nat32 { x **% y }; + /// Returns an iterator over `Nat32` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat32.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat32.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Nat32, toExclusive : Nat32) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Nat32 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Nat32` values from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat32.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat32.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Nat32, to : Nat32) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Nat32 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Nat32 values, from 0 to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat32.allValues(); + /// assert(?0 == iter.next()); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(0, maxValue) }; } diff --git a/src/Nat64.mo b/src/Nat64.mo index 71c83de5..a8f50f53 100644 --- a/src/Nat64.mo +++ b/src/Nat64.mo @@ -563,16 +563,97 @@ module { /// as a function value at the moment. public func powWrap(x : Nat64, y : Nat64) : Nat64 { x **% y }; + /// Returns an iterator over `Nat64` values from the first to second argument with an exclusive upper bound. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat64.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat64.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Nat64, toExclusive : Nat64) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Nat64 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Nat64` values from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat64.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat64.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Nat64, to : Nat64) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Nat64 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Nat64 values, from 0 to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat64.allValues(); + /// assert(?0 == iter.next()); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(0, maxValue) }; } diff --git a/src/Nat8.mo b/src/Nat8.mo index 80e5d9a4..9a7ad1d6 100644 --- a/src/Nat8.mo +++ b/src/Nat8.mo @@ -558,16 +558,97 @@ module { /// as a function value at the moment. public func powWrap(x : Nat8, y : Nat8) : Nat8 { x **% y }; + /// Returns an iterator over `Nat8` values from the first to second argument with an exclusive upper bound. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat8.range(1, 4); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat8.range(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func range(fromInclusive : Nat8, toExclusive : Nat8) : Iter.Iter { - todo() + if (fromInclusive >= toExclusive) { + Iter.empty() + } else { + object { + var n = fromInclusive; + public func next() : ?Nat8 { + if (n == toExclusive) { + null + } else { + let result = n; + n += 1; + ?result + } + } + } + } }; + /// Returns an iterator over `Nat8` values from the first to second argument, inclusive. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat8.rangeInclusive(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + /// + /// If the first argument is greater than the second argument, the function returns an empty iterator. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat8.rangeInclusive(4, 1); + /// assert(null == iter.next()); // empty iterator + /// ``` public func rangeInclusive(from : Nat8, to : Nat8) : Iter.Iter { - todo() + if (from > to) { + Iter.empty() + } else { + object { + var n = from; + var done = false; + public func next() : ?Nat8 { + if (done) { + null + } else { + let result = n; + if (n == to) { + done := true + } else { + n += 1 + }; + ?result + } + } + } + } }; + /// Returns an iterator over all Nat8 values, from 0 to maxValue. + /// ```motoko include=import + /// import Iter "mo:base/Iter"; + /// + /// let iter = Nat8.allValues(); + /// assert(?0 == iter.next()); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// // ... + /// ``` public func allValues() : Iter.Iter { - todo() + rangeInclusive(0, maxValue) }; } diff --git a/src/Principal.mo b/src/Principal.mo index 31cf8c15..934a1853 100644 --- a/src/Principal.mo +++ b/src/Principal.mo @@ -68,7 +68,7 @@ module { sha224.writeBlob(subAccount) }; case (null) { - let defaultSubAccount = Array.generate(32, func _ = 0); + let defaultSubAccount = Array.tabulate(32, func _ = 0); sha224.writeArray(defaultSubAccount) } }; @@ -406,8 +406,8 @@ module { var s6 : Nat32 = 0; var s7 : Nat32 = 0; - let msg : [var Nat32] = VarArray.init(16, 0); - let digest = VarArray.init(sum_bytes, 0); + let msg : [var Nat32] = VarArray.repeat(0, 16); + let digest = VarArray.repeat(0, sum_bytes); var word : Nat32 = 0; var i_msg : Nat8 = 0; diff --git a/src/Set.mo b/src/Set.mo index a725d9de..56293630 100644 --- a/src/Set.mo +++ b/src/Set.mo @@ -117,7 +117,11 @@ module { todo() }; - public func flatten(set : Iter.Iter>) : Set { + public func join(set : Iter.Iter>) : Set { + todo() + }; + + public func flatten(set : Set>) : Set { todo() }; diff --git a/src/Stack.mo b/src/Stack.mo index fda149fb..19b7b37e 100644 --- a/src/Stack.mo +++ b/src/Stack.mo @@ -109,9 +109,13 @@ module { todo() }; - // public func flatten(stack : Iter.Iter>) : Stack { - // todo() - // }; + public func join(stack : IterType.Iter>) : Stack { + todo() + }; + + public func flatten(stack : Stack>) : Stack { + todo() + }; public func take(stack : Stack, n : Nat) : Stack { todo() @@ -153,7 +157,7 @@ module { todo() }; - public func generate(n : Nat, f : Nat -> T) : Stack { + public func tabulate(n : Nat, f : Nat -> T) : Stack { todo() }; diff --git a/src/VarArray.mo b/src/VarArray.mo index a70d8616..f7467a37 100644 --- a/src/VarArray.mo +++ b/src/VarArray.mo @@ -3,18 +3,70 @@ import Iter "type/Iter"; import Order "Order"; import Result "Result"; +import Option "Option"; import Prim "mo:⛔"; import { todo } "Debug"; module { + /// Creates an empty mutable array (equivalent to `[var]`). public func empty() : [var T] = [var]; - public func init(size : Nat, initValue : T) : [var T] = Prim.Array_init(size, initValue); + /// Creates a mutable array containing `item` repeated `size` times. + /// + /// ```motoko include=import + /// let array = VarArray.repeat("Echo", 3); + /// assert array == [var "Echo", "Echo", "Echo"]; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func repeat(item : T, size : Nat) : [var T] = Prim.Array_init(size, item); - public func generate(size : Nat, generator : Nat -> T) : [var T] { + /// Duplicates `array`, returning a shallow copy of the original. + /// + /// ```motoko include=import + /// let array1 = [var 1, 2, 3]; + /// let array2 = VarArray.clone(array1); + /// array2[0] := 0; + /// assert array1 == [var 1, 2, 3]; + /// assert array2 = [var 0, 2, 3]; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func clone(array : [var T]) : [var T] { + let size = array.size(); if (size == 0) { - return [var]; + return [var] + }; + let newArray = Prim.Array_init(size, array[0]); + var i = 0; + while (i < size) { + newArray[i] := array[i]; + i += 1 + }; + newArray + }; + + /// Creates an immutable array of size `size`. Each element at index i + /// is created by applying `generator` to i. + /// + /// ```motoko include=import + /// let array : [var Nat] = VarArray.tabulate(4, func i = i * 2); + /// assert array == [var 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) : [var T] { + if (size == 0) { + return [var] }; let first = generator(0); let array = Prim.Array_init(size, first); @@ -22,27 +74,91 @@ module { var index = 1; while (index < size) { array[index] := generator(index); - index += 1; + index += 1 }; - array; - }; - - public func equal(array1 : [var T], array2 : [var T], equal : (T, T) -> Bool) : Bool { - todo() + array }; + /// Returns the first value in `array` for which `predicate` returns true. + /// If no element satisfies the predicate, returns null. + /// + /// ```motoko include=import + /// let array = [var 1, 9, 4, 8]; + /// VarArray.find(array, func x = x > 8) + /// ``` + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func find(array : [var T], predicate : T -> Bool) : ?T { - todo() + for (element in array.vals()) { + if (predicate element) { + return ?element + } + }; + null }; - public func append(array1 : [var T], array2 : [var T]) : [var T] { - todo() + /// Create a new mutable array by concatenating the values of `array1` and `array2`. + /// Note that `Array.append` copies its arguments and has linear complexity. + /// + /// ```motoko include=import + /// let array1 = [var 1, 2, 3]; + /// let array2 = [var 4, 5, 6]; + /// VarArray.concat(array1, array2) + /// ``` + /// Runtime: O(size1 + size2) + /// + /// Space: O(size1 + size2) + public func concat(array1 : [var T], array2 : [var T]) : [var T] { + let size1 = array1.size(); + let size2 = array2.size(); + tabulate( + size1 + size2, + func i { + if (i < size1) { + array1[i] + } else { + array2[i - size1] + } + } + ) }; + /// Sorts the elements in a mutable array according to `compare`. + /// Sort is deterministic and stable. + /// + /// ```motoko include=import + /// import Nat "mo:base/Nat"; + /// + /// let array = [var 4, 2, 6]; + /// VarArray.sort(array, Nat.compare) + /// ``` + /// Runtime: O(size * log(size)) + /// + /// Space: O(size) + /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func sort(array : [var T], compare : (T, T) -> Order.Order) : [var T] { - todo() + let newArray = clone(array); + sortInPlace(newArray, compare); + newArray }; + /// Sorts the elements in a mutable array in place according to `compare`. + /// Sort is deterministic and stable. + /// + /// ```motoko include=import + /// import Nat "mo:base/Nat"; + /// + /// let array = [var 4, 2, 6]; + /// VarArray.sortInPlace(array, Nat.compare); + /// assert array == [var 2, 4, 6]; + /// ``` + /// Runtime: O(size * log(size)) + /// + /// Space: O(size) + /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func sortInPlace(array : [var T], compare : (T, T) -> Order.Order) : () { // Stable merge sort in a bottom-up iterative style. Same algorithm as the sort in Buffer. let size = array.size(); @@ -107,104 +223,805 @@ module { } }; + /// Creates a new mutable array by reversing the order of elements in `array`. + /// + /// ```motoko include=import + /// + /// let array = [var 10, 11, 12]; + /// + /// VarArray.reverse(array) + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) public func reverse(array : [var T]) : [var T] { - todo() + let size = array.size(); + tabulate(size, func i = array[size - i - 1]) }; + /// Reverses the order of elements in a mutable array in place. + /// + /// ```motoko include=import + /// let array = [var 10, 11, 12]; + /// VarArray.reverseInPlace(array); + /// assert array == [var 12, 11, 10]; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) public func reverseInPlace(array : [var T]) : () { - todo() + let size = array.size(); + if (size == 0) { + return + }; + var i = 0; + var j = (size - 1) : Nat; + while (i < j) { + let temp = array[i]; + array[i] := array[j]; + array[j] := temp; + i += 1; + j -= 1 + } }; + /// Calls `f` with each element in `array`. + /// Retains original ordering of elements. + /// + /// ```motoko include=import + /// import Debug "mo:base/Debug"; + /// + /// let array = [var 0, 1, 2, 3]; + /// VarArray.forEach(array, func(x) { + /// Debug.print(debug_show x) + /// }) + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. public func forEach(array : [var T], f : T -> ()) { - todo() + for (item in array.vals()) { + f(item) + } }; - public func map(array : [var T], f : T -> Y) : [var Y] { - generate(array.size(), func (index) { - f(array[index]) - }) + /// Creates a new mutable array by applying `f` to each element in `array`. `f` "maps" + /// each element it is applied to of type `X` to an element of type `Y`. + /// Retains original ordering of elements. + /// + /// ```motoko include=import + /// + /// let array = [var 0, 1, 2, 3]; + /// VarArray.map(array, func x = x * 3) + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func map(array : [var T], f : T -> R) : [var R] { + tabulate( + array.size(), + func(index) { + f(array[index]) + } + ) }; + /// Applies `f` to each element of `array` in place, + /// retaining the original ordering of elements. + /// + /// ```motoko include=import + /// + /// let array = [var 0, 1, 2, 3]; + /// VarArray.mapInPlace(array, func x = x * 3) + /// assert array == [0, 2, 4, 6]; + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapInPlace(array : [var T], f : T -> T) { + var index = 0; + let size = array.size(); + while (index < size) { + array[index] := f(array[index]); + index += 1 + } + }; + + /// Creates a new mutable array by applying `predicate` to every element + /// in `array`, retaining the elements for which `predicate` returns true. + /// + /// ```motoko include=import + /// let array = [var 4, 2, 6, 1, 5]; + /// let evenElements = VarArray.filter(array, func x = x % 2 == 0); + /// ``` + /// Runtime: O(size) + /// + /// Space: O(size) + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func filter(array : [var T], f : T -> Bool) : [var T] { - todo() + var count = 0; + let keep = Prim.Array_tabulate( + array.size(), + func i { + if (f(array[i])) { + count += 1; + true + } else { + false + } + } + ); + var nextKeep = 0; + tabulate( + count, + func _ { + while (not keep[nextKeep]) { + nextKeep += 1 + }; + nextKeep += 1; + array[nextKeep - 1] + } + ) }; - public func filterMap(array : [var T], f : T -> ?Y) : [var Y] { - todo() + /// Creates a new array by applying `f` to each element in `array`, + /// and keeping all non-null elements. The ordering is retained. + /// + /// ```motoko include=import + /// import {toText} "mo:base/Nat"; + /// + /// let array = [var 4, 2, 0, 1]; + /// let newArray = + /// VarArray.filterMap( // mapping from Nat to Text values + /// array, + /// func x = if (x == 0) { null } else { ?toText(100 / x) } // can't divide by 0, so return null + /// ); + /// ``` + /// Runtime: O(size) + /// + /// Space: O(size) + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func filterMap(array : [var T], f : T -> ?R) : [var R] { + var count = 0; + let options = Prim.Array_tabulate( + array.size(), + func i { + let result = f(array[i]); + switch (result) { + case (?element) { + count += 1; + result + }; + case null { + null + } + } + } + ); + + var nextSome = 0; + tabulate( + count, + func _ { + while (Option.isNull(options[nextSome])) { + nextSome += 1 + }; + nextSome += 1; + switch (options[nextSome - 1]) { + case (?element) element; + case null { + Prim.trap "Malformed array in filterMap" + } + } + } + ) }; - public func mapResult(array : [var T], f : T -> Result.Result) : Result.Result<[var Y], E> { - todo() + /// Creates a new array by applying `f` to each element in `array`. + /// If any invocation of `f` produces an `#err`, returns an `#err`. Otherwise + /// returns an `#ok` containing the new array. + /// + /// ```motoko include=import + /// let array = [var 4, 3, 2, 1, 0]; + /// // divide 100 by every element in the array + /// VarArray.mapResult(array, func x { + /// if (x > 0) { + /// #ok(100 / x) + /// } else { + /// #err "Cannot divide by zero" + /// } + /// }) + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapResult(array : [var T], f : T -> Result.Result) : Result.Result<[var R], E> { + let size = array.size(); + + var error : ?Result.Result<[var R], E> = null; + let results = tabulate( + size, + func i { + switch (f(array[i])) { + case (#ok element) { + ?element + }; + case (#err e) { + switch (error) { + case null { + // only take the first error + error := ?(#err e) + }; + case _ {} + }; + null + } + } + } + ); + + switch error { + case null { + // unpack the option + #ok( + map( + results, + func element { + switch element { + case (?element) { + element + }; + case null { + Prim.trap "Malformed array in mapResults" + } + } + } + ) + ) + }; + case (?error) { + error + } + } }; - public func mapEntries(array : [var T], f : (T, Nat) -> Y) : [var Y] { - todo() + /// Creates a new array by applying `f` to each element in `array` and its index. + /// Retains original ordering of elements. + /// + /// ```motoko include=import + /// + /// let array = [10, 10, 10, 10]; + /// Array.mapEntries(array, func (x, i) = i * x) + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapEntries(array : [var T], f : (T, Nat) -> R) : [var R] { + tabulate(array.size(), func i = f(array[i], i)) }; - public func flatMap(array : [var T], k : T -> [var R]) : [var R] { - todo() + /// Creates a new array by applying `k` to each element in `array`, + /// and concatenating the resulting arrays in order. + /// + /// ```motoko include=import + /// import Nat "mo:base/Nat"; + /// + /// let array = [var 1, 2, 3, 4]; + /// VarArray.flatMap(array, func x = [x, -x]) + /// + /// ``` + /// Runtime: O(size) + /// + /// Space: O(size) + /// *Runtime and space assumes that `k` runs in O(1) time and space. + public func flatMap(array : [var T], k : T -> Iter.Iter) : [var R] { + var flatSize = 0; + let arrays = Prim.Array_tabulate<[var R]>( + array.size(), + func i { + let subArray = fromIter(k(array[i])); // TODO: optimize + flatSize += subArray.size(); + subArray + } + ); + + // could replace with a call to flatten, + // but it would require an extra pass (to compute `flatSize`) + var outer = 0; + var inner = 0; + tabulate( + flatSize, + func _ { + while (inner == arrays[outer].size()) { + inner := 0; + outer += 1 + }; + let element = arrays[outer][inner]; + inner += 1; + element + } + ) }; + /// Collapses the elements in `array` into a single value by starting with `base` + /// and progessively combining elements into `base` with `combine`. Iteration runs + /// left to right. + /// + /// ```motoko include=import + /// import {add} "mo:base/Nat"; + /// + /// let array = [var 4, 2, 0, 1]; + /// let sum = + /// VarArray.foldLeft( + /// array, + /// 0, // start the sum at 0 + /// func(sumSoFar, x) = sumSoFar + x // this entire function can be replaced with `add`! + /// ); + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `combine` runs in O(1) time and space. public func foldLeft(array : [var T], base : A, combine : (A, T) -> A) : A { - todo() + var acc = base; + for (element in array.vals()) { + acc := combine(acc, element) + }; + acc }; + /// Collapses the elements in `array` into a single value by starting with `base` + /// and progessively combining elements into `base` with `combine`. Iteration runs + /// right to left. + /// + /// ```motoko include=import + /// import {toText} "mo:base/Nat"; + /// + /// let array = [1, 9, 4, 8]; + /// let bookTitle = VarArray.foldRight(array, "", func(x, acc) = toText(x) # acc); + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `combine` runs in O(1) time and space. public func foldRight(array : [var T], base : A, combine : (T, A) -> A) : A { - todo() + var acc = base; + let size = array.size(); + var i = size; + while (i > 0) { + i -= 1; + acc := combine(array[i], acc) + }; + acc + }; + + /// Combines an iterator of mutable arrays into a single mutable array. Retains the original + /// ordering of the elements. + /// + /// Consider using `VarArray.flatten()` where possible for better performance. + /// + /// ```motoko include=import + /// + /// let arrays = [[var 0, 1, 2], [var 2, 3], [var], [var 4]]; + /// VarArray.join(VarArray.fromIter(arrays)) // => [var 0, 1, 2, 2, 3, 4] + /// ``` + /// + /// Runtime: O(number of elements in array) + /// + /// Space: O(number of elements in array) + public func join(arrays : Iter.Iter<[var T]>) : [var T] { + flatten(fromIter(arrays)) }; - public func flatten(arrays : Iter.Iter<[var T]>) : [var T] { - todo() + /// Combines a mutable array of mutable arrays into a single mutable array. Retains the original + /// ordering of the elements. + /// + /// This has better performance compared to `VarArray.flatten()`. + /// + /// ```motoko include=import + /// + /// let arrays = [var [var 0, 1, 2], [var 2, 3], [var], [var 4]]; + /// VarArray.flatten(arrays) // => [var 0, 1, 2, 2, 3, 4] + /// ``` + /// + /// Runtime: O(number of elements in array) + /// + /// Space: O(number of elements in array) + public func flatten(arrays : [var [var T]]) : [var T] { + var flatSize = 0; + for (subArray in arrays.vals()) { + flatSize += subArray.size() + }; + + var outer = 0; + var inner = 0; + tabulate( + flatSize, + func _ { + while (inner == arrays[outer].size()) { + inner := 0; + outer += 1 + }; + let element = arrays[outer][inner]; + inner += 1; + element + } + ) }; + /// Create an array containing a single value. + /// + /// ```motoko include=import + /// let array = VarArray.singleton(2); + /// assert array == [var 2]; + /// ``` + /// + /// Runtime: O(1) + /// + /// Space: O(1) public func singleton(element : T) : [var T] = [var element]; + /// Returns the size of a mutable array. Equivalent to `array.size()`. public func size(array : [var T]) : Nat = array.size(); + /// Returns whether a mutable array is empty, i.e. contains zero elements. public func isEmpty(array : [var T]) : Bool = array.size() == 0; + /// Converts an iterator to a mutable array. public func fromIter(iter : Iter.Iter) : [var T] { - todo() + todo() // See `Array.fromIter()` }; + /// Returns an iterator (`Iter`) over the indices of `array`. + /// An iterator provides a single method `next()`, which returns + /// indices in order, or `null` when out of index to iterate over. + /// + /// NOTE: You can also use `array.keys()` instead of this function. See example + /// below. + /// + /// ```motoko include=import + /// let array = [var 10, 11, 12]; + /// + /// var sum = 0; + /// for (element in array.keys()) { + /// sum += element; + /// }; + /// sum + /// ``` + /// + /// Runtime: O(1) + /// + /// Space: O(1) public func keys(array : [var T]) : Iter.Iter = array.keys(); + /// Iterator provides a single method `next()`, which returns + /// elements in order, or `null` when out of elements to iterate over. + /// + /// Note: You can also use `array.values()` instead of this function. See example + /// below. + /// + /// ```motoko include=import + /// let array = [var 10, 11, 12]; + /// + /// var sum = 0; + /// for (element in array.values()) { + /// sum += element; + /// }; + /// sum + /// ``` + /// + /// Runtime: O(1) + /// + /// Space: O(1) public func values(array : [var T]) : Iter.Iter = array.vals(); + /// Iterator provides a single method `next()`, which returns + /// pairs of (index, element) in order, or `null` when out of elements to iterate over. + /// + /// ```motoko include=import + /// let array = [var 10, 11, 12]; + /// + /// var sum = 0; + /// for ((index, element) in Array.enumerate(array)) { + /// sum += element; + /// }; + /// sum // => 33 + /// ``` + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func enumerate(array : [var T]) : Iter.Iter<(Nat, T)> = object { + let size = array.size(); + var index = 0; + public func next() : ?(Nat, T) { + if (index > size) { + return null + }; + let i = index; + index += 1; + ?(i, array[i]) + } + }; + + /// Returns true if all elements in `array` satisfy the predicate function. + /// + /// ```motoko include=import + /// let array = [var 1, 2, 3, 4]; + /// VarArray.all(array, func x = x > 0) // => true + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func all(array : [var T], predicate : T -> Bool) : Bool { - todo() + for (element in array.vals()) { + if (not predicate(element)) { + return false + } + }; + true }; + /// Returns true if any element in `array` satisfies the predicate function. + /// + /// ```motoko include=import + /// let array = [var 1, 2, 3, 4]; + /// VarArray.any(array, func x = x > 3) // => true + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. public func any(array : [var T], predicate : T -> Bool) : Bool { - todo() + for (element in array.vals()) { + if (predicate(element)) { + return true + } + }; + false }; + /// Returns a new sub-array from the given array provided the start index and length of elements in the sub-array. + /// + /// Limitations: Traps if the start index + length is greater than the size of the array + /// + /// ```motoko include=import + /// + /// let array = [1, 2, 3, 4, 5]; + /// let subArray = VarArray.subArray(array, 2, 3); + /// ``` + /// Runtime: O(length) + /// + /// Space: O(length) public func subArray(array : [var T], start : Nat, length : Nat) : [var T] { - todo() + if (start + length > array.size()) { Prim.trap("Array.subArray()") }; + tabulate(length, func i = array[start + i]) }; + /// Returns the index of the first `element` in the `array`. + /// + /// ```motoko include=import + /// import Char "mo:base/Char"; + /// let array = [var 'c', 'o', 'f', 'f', 'e', 'e']; + /// assert VarArray.indexOf('c', array, Char.equal) == ?0; + /// assert VarArray.indexOf('f', array, Char.equal) == ?2; + /// assert VarArray.indexOf('g', array, Char.equal) == null; + /// ``` + /// + /// Runtime: O(array.size()) + /// + /// Space: O(1) public func indexOf(element : T, array : [var T], equal : (T, T) -> Bool) : ?Nat = nextIndexOf(element, array, 0, equal); + /// Returns the index of the next occurence of `element` in the `array` starting from the `from` index (inclusive). + /// + /// ```motoko include=import + /// import Char "mo:base/Char"; + /// let array = [var 'c', 'o', 'f', 'f', 'e', 'e']; + /// assert VarArray.nextIndexOf('c', array, 0, Char.equal) == ?0; + /// assert VarArray.nextIndexOf('f', array, 0, Char.equal) == ?2; + /// assert VarArray.nextIndexOf('f', array, 2, Char.equal) == ?2; + /// assert VarArray.nextIndexOf('f', array, 3, Char.equal) == ?3; + /// assert VarArray.nextIndexOf('f', array, 4, Char.equal) == null; + /// ``` + /// + /// Runtime: O(array.size()) + /// + /// Space: O(1) public func nextIndexOf(element : T, array : [var T], fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat { - todo() + var index = fromInclusive; + let size = array.size(); + while (index < size) { + if (equal(array[index], element)) { + return ?index + } else { + index += 1 + } + }; + null }; + /// Returns the index of the last `element` in the `array`. + /// + /// ```motoko include=import + /// import Char "mo:base/Char"; + /// let array = [var 'c', 'o', 'f', 'f', 'e', 'e']; + /// assert VarArray.lastIndexOf('c', array, Char.equal) == ?0; + /// assert VarArray.lastIndexOf('f', array, Char.equal) == ?3; + /// assert VarArray.lastIndexOf('e', array, Char.equal) == ?5; + /// assert VarArray.lastIndexOf('g', array, Char.equal) == null; + /// ``` + /// + /// Runtime: O(array.size()) + /// + /// Space: O(1) public func lastIndexOf(element : T, array : [var T], equal : (T, T) -> Bool) : ?Nat = prevIndexOf(element, array, array.size(), equal); + /// Returns the index of the previous occurence of `element` in the `array` starting from the `from` index (exclusive). + /// + /// ```motoko include=import + /// import Char "mo:base/Char"; + /// let array = [var 'c', 'o', 'f', 'f', 'e', 'e']; + /// assert VarArray.prevIndexOf('c', array, array.size(), Char.equal) == ?0; + /// assert VarArray.prevIndexOf('e', array, array.size(), Char.equal) == ?5; + /// assert VarArray.prevIndexOf('e', array, 5, Char.equal) == ?4; + /// assert VarArray.prevIndexOf('e', array, 4, Char.equal) == null; + /// ``` + /// + /// Runtime: O(array.size()); + /// Space: O(1); public func prevIndexOf(element : T, array : [var T], fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat { - todo() + var i = fromExclusive; + while (i > 0) { + i -= 1; + if (equal(array[i], element)) { + return ?i + } + }; + null }; - public func slice(array : [var T], fromInclusive : Int, toExclusive : Int) : Iter.Iter { - todo() + /// Returns an iterator over a slice of the given array. + /// + /// ```motoko include=import + /// let array = [var 1, 2, 3, 4, 5]; + /// let s = VarArray.range(array, 3, array.size()); + /// assert s.next() == ?4; + /// assert s.next() == ?5; + /// assert s.next() == null; + /// + /// let s = Array.range(array, 0, 0); + /// assert s.next() == null; + /// ``` + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func range(array : [var T], fromInclusive : Int, toExclusive : Int) : Iter.Iter { + let size = array.size(); + // Convert negative indices to positive and handle bounds + 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 } + }; + // Convert to Nat (values are non-negative due to bounds checking above) + let start = Prim.abs(startInt); + let end = Prim.abs(endInt); + object { + var pos = start; + public func next() : ?T { + if (pos >= end) { + null + } else { + let elem = array[pos]; + pos += 1; + ?elem + } + } + } }; + /// Converts the mutable array to its textual representation using `f` to convert each element to `Text`. + /// + /// ```motoko include=import + /// import Nat "mo:base/Nat"; + /// let array = [var 1, 2, 3]; + /// VarArray.toText(array, Nat.toText) // => "[var 1, 2, 3]" + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. public func toText(array : [var T], f : T -> Text) : Text { - todo() + let size = array.size(); + if (size == 0) { return "[var]" }; + var text = "[var "; + var i = 0; + while (i < size) { + if (i != 0) { + text #= ", " + }; + text #= f(array[i]); + i += 1 + }; + text #= "]"; + text }; + /// Compares two mutable arrays using the provided comparison function for elements. + /// Returns #less, #equal, or #greater if `array1` is less than, equal to, + /// or greater than `array2` respectively. + /// + /// If arrays have different sizes but all elements up to the shorter length are equal, + /// the shorter array is considered #less than the longer array. + /// + /// ```motoko include=import + /// import Nat "mo:base/Nat"; + /// let array1 = [var 1, 2, 3]; + /// let array2 = [var 1, 2, 4]; + /// VarArray.compare(array1, array2, Nat.compare) // => #less + /// + /// let array3 = [var 1, 2]; + /// let array4 = [var 1, 2, 3]; + /// VarArray.compare(array3, array4, Nat.compare) // => #less (shorter array) + /// ``` + /// + /// Runtime: O(min(size1, size2)) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. public func compare(array1 : [var T], array2 : [var T], compare : (T, T) -> Order.Order) : Order.Order { - todo() + let size1 = array1.size(); + let size2 = array2.size(); + var i = 0; + let minSize = if (size1 < size2) { size1 } else { size2 }; + while (i < minSize) { + switch (compare(array1[i], array2[i])) { + case (#less) { return #less }; + case (#greater) { return #greater }; + case (#equal) { i += 1 } + } + }; + if (size1 < size2) { #less } else if (size1 > size2) { #greater } else { + #equal + } }; } diff --git a/src/immutable/Stack.mo b/src/immutable/Stack.mo index acc578f1..1182982e 100644 --- a/src/immutable/Stack.mo +++ b/src/immutable/Stack.mo @@ -66,7 +66,11 @@ module { todo() }; - public func flatten(stack : Iter.Iter>) : Stack { + public func join(stack : Iter.Iter>) : Stack { + todo() + }; + + public func flatten(stack : Stack>) : Stack { todo() }; @@ -106,7 +110,7 @@ module { todo() }; - public func generate(n : Nat, f : Nat -> T) : Stack { + public func tabulate(n : Nat, f : Nat -> T) : Stack { todo() }; diff --git a/test/Map.test.mo b/test/Map.test.mo index 2289b3b9..f196cd6d 100644 --- a/test/Map.test.mo +++ b/test/Map.test.mo @@ -664,14 +664,14 @@ run( M.equals( T.array<(Nat, Text)>( entryTestable, - Array.generate<(Nat, Text)>(smallSize, func(index) { (index, Nat.toText(index)) }) + Array.tabulate<(Nat, Text)>(smallSize, func(index) { (index, Nat.toText(index)) }) ) ) ), test( "iterate backward", Iter.toArray(Map.reverseEntries(smallMap())), - M.equals(T.array<(Nat, Text)>(entryTestable, Array.reverse(Array.generate<(Nat, Text)>(smallSize, func(index) { (index, Nat.toText(index)) })))) + M.equals(T.array<(Nat, Text)>(entryTestable, Array.reverse(Array.tabulate<(Nat, Text)>(smallSize, func(index) { (index, Nat.toText(index)) })))) ), test( "contains present keys", @@ -808,17 +808,17 @@ run( test( "iterate keys", Iter.toArray(Map.keys(smallMap())), - M.equals(T.array(T.natTestable, Array.generate(smallSize, func(index) { index }))) + M.equals(T.array(T.natTestable, Array.tabulate(smallSize, func(index) { index }))) ), test( "iterate values", Iter.toArray(Map.values(smallMap())), - M.equals(T.array(T.textTestable, Array.generate(smallSize, func(index) { Nat.toText(index) }))) + M.equals(T.array(T.textTestable, Array.tabulate(smallSize, func(index) { Nat.toText(index) }))) ), test( "from iterator", do { - let array = Array.generate<(Nat, Text)>(smallSize, func(index) { (index, Nat.toText(index)) }); + let array = Array.tabulate<(Nat, Text)>(smallSize, func(index) { (index, Nat.toText(index)) }); let map = Map.fromIter(Iter.fromArray(array), Nat.compare); for (index in Nat.range(0, smallSize)) { assert (Map.get(map, Nat.compare, index) == ?Nat.toText(index)) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 67fbe903..683de424 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -7,34 +7,36 @@ "public func compare(array1 : [T], array2 : [T], compare : (T, T) -> Order.Order) : Order.Order", "public func concat(array1 : [T], array2 : [T]) : [T]", "public func empty() : [T]", + "public func enumerate(array : [var T]) : Iter.Iter<(Nat, T)>", "public func equal(array1 : [T], array2 : [T], equal : (T, T) -> Bool) : Bool", "public func filter(array : [T], f : T -> Bool) : [T]", - "public func filterMap(array : [T], f : T -> ?Y) : [Y]", + "public func filterMap(array : [T], f : T -> ?R) : [R]", "public func find(array : [T], predicate : T -> Bool) : ?T", - "public func flatMap(array : [T], k : T -> [R]) : [R]", - "public func flatten(arrays : Iter.Iter<[T]>) : [T]", + "public func flatMap(array : [T], k : T -> Iter.Iter) : [R]", + "public func flatten(arrays : [[T]]) : [T]", "public func foldLeft(array : [T], base : A, combine : (A, T) -> A) : A", "public func foldRight(array : [T], base : A, combine : (T, A) -> A) : A", "public func forEach(array : [T], f : T -> ())", "public func fromIter(iter : Iter.Iter) : [T]", "public func fromVarArray(varArray : [var T]) : [T]", - "public func generate(size : Nat, generator : Nat -> T) : [T]", "public func indexOf(element : T, array : [T], equal : (T, T) -> Bool) : ?Nat", - "public func init(size : Nat, initValue : T) : [T]", "public func isEmpty(array : [T]) : Bool", + "public func join(arrays : Iter.Iter<[T]>) : [T]", "public func keys(array : [T]) : Iter.Iter", "public func lastIndexOf(element : T, array : [T], equal : (T, T) -> Bool) : ?Nat", - "public func map(array : [T], f : T -> Y) : [Y]", + "public func map(array : [T], f : T -> R) : [R]", "public func mapEntries(array : [T], f : (T, Nat) -> R) : [R]", - "public func mapResult(array : [T], f : T -> Result.Result) : Result.Result<[Y], E>", + "public func mapResult(array : [T], f : T -> Result.Result) : Result.Result<[R], E>", "public func nextIndexOf(element : T, array : [T], fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat", "public func prevIndexOf(element : T, array : [T], fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat", + "public func range(array : [T], fromInclusive : Int, toExclusive : Int) : Iter.Iter", + "public func repeat(item : T, size : Nat) : [T]", "public func reverse(array : [T]) : [T]", "public func singleton(element : T) : [T]", "public func size(array : [T]) : Nat", - "public func slice(array : [T], fromInclusive : Int, toExclusive : Int) : Iter.Iter", "public func sort(array : [T], compare : (T, T) -> Order.Order) : [T]", "public func subArray(array : [T], start : Nat, length : Nat) : [T]", + "public func tabulate(size : Nat, generator : Nat -> T) : [T]", "public func toText(array : [T], f : T -> Text) : Text", "public func toVarArray(array : [T]) : [var T]", "public func values(array : [T]) : Iter.Iter" @@ -464,6 +466,7 @@ "name": "Iter", "exports": [ "public func concat(a : Iter, b : Iter) : Iter", + "public func empty() : Iter", "public func enumerate(xs : Iter) : Iter<(Nat, T)>", "public func filter(xs : Iter, f : T -> Bool) : Iter", "public func forEach( xs : Iter, f : (T) -> () )", @@ -498,7 +501,7 @@ "public func filterMap(list : List, f : T1 -> ?T2) : List", "public func first(list : List) : T", "public func flatMap(list : List, k : T1 -> Iter.Iter) : List", - "public func flatten(lists : Iter.Iter>) : List", + "public func flatten(lists : List>) : List", "public func foldLeft(list : List, base : A, combine : (A, T) -> A) : A", "public func foldRight(list : List, base : A, combine : (T, A) -> A) : A", "public func forEach(list : List, f : T -> ())", @@ -511,6 +514,7 @@ "public func isEmpty(list : List) : Bool", "public func isPrefixOf(list : List, prefix : List, equal : (T, T) -> Bool) : Bool", "public func isSuffixOf(list : List, suffix : List, equal : (T, T) -> Bool) : Bool", + "public func join(lists : Iter.Iter>) : List", "public func last(list : List) : T", "public func lastIndexOf(list : List, element : T, equal : (T, T) -> Bool) : ?Nat", "public type List", @@ -984,7 +988,7 @@ "public func equal(set1 : Set, set2 : Set, equal : (T, T) -> Bool) : Bool", "public func filter(set : Set, compare : (T, T) -> Order.Order, f : T -> Bool) : Set", "public func filterMap(set : Set, compare : (T2, T2) -> Order.Order, f : T1 -> ?T2) : Set", - "public func flatten(set : Iter.Iter>) : Set", + "public func flatten(set : Set>) : Set", "public func foldLeft( set : Set, base : A, combine : (A, T) -> A ) : A", "public func foldRight( set : Set, base : A, combine : (A, T) -> A ) : A", "public func forEach(set : Set, f : T -> ())", @@ -993,6 +997,7 @@ "public func intersect(set1 : Set, set2 : Set) : Set", "public func isEmpty(set : Set) : Bool", "public func isSubset(set1 : Set, set2 : Set) : Bool", + "public func join(set : Iter.Iter>) : Set", "public func map(set : Set, compare : (T2, T2) -> Order.Order, f : T1 -> T2) : Set", "public func max(set : Set) : ?T", "public func min(set : Set) : ?T", @@ -1023,14 +1028,15 @@ "public func filter(stack : Stack, f : T -> Bool) : Stack", "public func filterMap(stack : Stack, f : T -> ?U) : Stack", "public func find(stack : Stack, f : T -> Bool) : ?T", + "public func flatten(stack : Stack>) : Stack", "public func foldLeft(stack : Stack, base : A, combine : (A, T) -> A) : A", "public func foldRight(stack : Stack, base : A, combine : (T, A) -> A) : A", "public func forEach(stack : Stack, f : T -> ())", "public func freeze(stack : Stack) : Immutable.Stack", "public func fromIter(iter : IterType.Iter) : Stack", - "public func generate(n : Nat, f : Nat -> T) : Stack", "public func get(stack : Stack, n : Nat) : ?T", "public func isEmpty(stack : Stack) : Bool", + "public func join(stack : IterType.Iter>) : Stack", "public func map(stack : Stack, f : T1 -> T2) : Stack", "public func merge(stack1 : Stack, stack2 : Stack, lessThanOrEqual : (T, T) -> Bool) : Stack", "public func partition(stack : Stack, f : T -> Bool) : (Stack, Stack)", @@ -1043,6 +1049,7 @@ "public func size(stack : Stack) : Nat", "public func split(stack : Stack, n : Nat) : (Stack, Stack)", "public type Stack", + "public func tabulate(n : Nat, f : Nat -> T) : Stack", "public func take(stack : Stack, n : Nat) : Stack", "public func thaw(stack : Immutable.Stack) : Stack", "public func toText(stack : Stack, f : T -> Text) : Text", @@ -1121,38 +1128,41 @@ "exports": [ "public func all(array : [var T], predicate : T -> Bool) : Bool", "public func any(array : [var T], predicate : T -> Bool) : Bool", - "public func append(array1 : [var T], array2 : [var T]) : [var T]", + "public func clone(array : [var T]) : [var T]", "public func compare(array1 : [var T], array2 : [var T], compare : (T, T) -> Order.Order) : Order.Order", + "public func concat(array1 : [var T], array2 : [var T]) : [var T]", "public func empty() : [var T]", - "public func equal(array1 : [var T], array2 : [var T], equal : (T, T) -> Bool) : Bool", + "public func enumerate(array : [var T]) : Iter.Iter<(Nat, T)>", "public func filter(array : [var T], f : T -> Bool) : [var T]", - "public func filterMap(array : [var T], f : T -> ?Y) : [var Y]", + "public func filterMap(array : [var T], f : T -> ?R) : [var R]", "public func find(array : [var T], predicate : T -> Bool) : ?T", - "public func flatMap(array : [var T], k : T -> [var R]) : [var R]", - "public func flatten(arrays : Iter.Iter<[var T]>) : [var T]", + "public func flatMap(array : [var T], k : T -> Iter.Iter) : [var R]", + "public func flatten(arrays : [var [var T]]) : [var T]", "public func foldLeft(array : [var T], base : A, combine : (A, T) -> A) : A", "public func foldRight(array : [var T], base : A, combine : (T, A) -> A) : A", "public func forEach(array : [var T], f : T -> ())", "public func fromIter(iter : Iter.Iter) : [var T]", - "public func generate(size : Nat, generator : Nat -> T) : [var T]", "public func indexOf(element : T, array : [var T], equal : (T, T) -> Bool) : ?Nat", - "public func init(size : Nat, initValue : T) : [var T]", "public func isEmpty(array : [var T]) : Bool", + "public func join(arrays : Iter.Iter<[var T]>) : [var T]", "public func keys(array : [var T]) : Iter.Iter", "public func lastIndexOf(element : T, array : [var T], equal : (T, T) -> Bool) : ?Nat", - "public func map(array : [var T], f : T -> Y) : [var Y]", - "public func mapEntries(array : [var T], f : (T, Nat) -> Y) : [var Y]", - "public func mapResult(array : [var T], f : T -> Result.Result) : Result.Result<[var Y], E>", + "public func map(array : [var T], f : T -> R) : [var R]", + "public func mapEntries(array : [var T], f : (T, Nat) -> R) : [var R]", + "public func mapInPlace(array : [var T], f : T -> T)", + "public func mapResult(array : [var T], f : T -> Result.Result) : Result.Result<[var R], E>", "public func nextIndexOf(element : T, array : [var T], fromInclusive : Nat, equal : (T, T) -> Bool) : ?Nat", "public func prevIndexOf(element : T, array : [var T], fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat", + "public func range(array : [var T], fromInclusive : Int, toExclusive : Int) : Iter.Iter", + "public func repeat(item : T, size : Nat) : [var T]", "public func reverse(array : [var T]) : [var T]", "public func reverseInPlace(array : [var T]) : ()", "public func singleton(element : T) : [var T]", "public func size(array : [var T]) : Nat", - "public func slice(array : [var T], fromInclusive : Int, toExclusive : Int) : Iter.Iter", "public func sort(array : [var T], compare : (T, T) -> Order.Order) : [var T]", "public func sortInPlace(array : [var T], compare : (T, T) -> Order.Order) : ()", "public func subArray(array : [var T], start : Nat, length : Nat) : [var T]", + "public func tabulate(size : Nat, generator : Nat -> T) : [var T]", "public func toText(array : [var T], f : T -> Text) : Text", "public func values(array : [var T]) : Iter.Iter" ] @@ -1263,16 +1273,16 @@ "public func filter(stack : Stack, f : T -> Bool) : Stack", "public func filterMap(stack : Stack, f : T -> ?U) : Stack", "public func find(stack : Stack, f : T -> Bool) : ?T", - "public func flatten(stack : Iter.Iter>) : Stack", + "public func flatten(stack : Stack>) : Stack", "public func foldLeft(stack : Stack, base : A, combine : (A, T) -> A) : A", "public func foldRight(stack : Stack, base : A, combine : (T, A) -> A) : A", "public func forEach(stack : Stack, f : T -> ())", "public func fromArray(array : [T]) : Stack", "public func fromIter(iter : Iter.Iter) : Stack", "public func fromVarArray(array : [var T]) : Stack", - "public func generate(n : Nat, f : Nat -> T) : Stack", "public func get(stack : Stack, n : Nat) : ?T", "public func isEmpty(stack : Stack) : Bool", + "public func join(stack : Iter.Iter>) : Stack", "public func last(stack : Stack) : ?T", "public func map(stack : Stack, f : T1 -> T2) : Stack", "public func mapResult(stack : Stack, f : T -> Result.Result) : Result.Result, E>", @@ -1286,6 +1296,7 @@ "public func size(stack : Stack) : Nat", "public func split(stack : Stack, n : Nat) : (Stack, Stack)", "public type Stack", + "public func tabulate(n : Nat, f : Nat -> T) : Stack", "public func take(stack : Stack, n : Nat) : Stack", "public func toArray(stack : Stack) : [T]", "public func toText(stack : Stack, f : T -> Text) : Text",