diff --git a/.gitignore b/.gitignore index 2b2ea0a2..d79ad137 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ +# Builds _build _out + +# Macs +.DS_Store + +# IDEs +.vscode/ diff --git a/src/Array.mo b/src/Array.mo index 12177c2e..3bc0d628 100644 --- a/src/Array.mo +++ b/src/Array.mo @@ -1,6 +1,5 @@ /// Functions on Arrays -import Buffer "Buffer"; import I "IterType"; import Option "Option"; import Order "Order"; @@ -42,7 +41,7 @@ module { }; }; - /// Sorts the given array according to the `cmp` function. + /// Sorts the given array, in ascending order, according to the `compare` function. /// This is a _stable_ sort. /// /// ```motoko @@ -51,13 +50,13 @@ module { /// let xs = [4, 2, 6]; /// assert(Array.sort(xs, Nat.compare) == [2, 4, 6]) /// ``` - public func sort(xs : [A], cmp : (A, A) -> Order.Order) : [A] { + public func sort(xs : [A], compare : (A, A) -> Order.Order) : [A] { let tmp : [var A] = thaw(xs); - sortInPlace(tmp, cmp); + sortInPlace(tmp, compare); freeze(tmp) }; - /// Sorts the given array in place according to the `cmp` function. + /// Sorts the given array, in ascending order, in place, according to the `compare` function. /// This is a _stable_ sort. /// /// ```motoko @@ -67,7 +66,7 @@ module { /// Array.sortInPlace(xs, Nat.compare); /// assert(Array.freeze(xs) == [1, 2, 4, 5, 6]) /// ``` - public func sortInPlace(xs : [var A], cmp : (A, A) -> Order.Order) { + public func sortInPlace(xs : [var A], compare : (A, A) -> Order.Order) { if (xs.size() < 2) return; let aux : [var A] = tabulateVar(xs.size(), func i { xs[i] }); @@ -87,7 +86,7 @@ module { } else if (j > hi) { xs[k] := aux[i]; i += 1; - } else if (Order.isLess(cmp(aux[j], aux[i]))) { + } else if (Order.isLess(compare(aux[j], aux[i]))) { xs[k] := aux[j]; j += 1; } else { @@ -119,25 +118,70 @@ module { }; /// Output array contains each array-value if and only if the predicate is true; ordering retained. public func filter(xs : [A], f : A -> Bool) : [A] { - let ys : Buffer.Buffer = Buffer.Buffer(xs.size()); - for (x in xs.vals()) { - if (f(x)) { - ys.add(x); - }; - }; - ys.toArray(); + var count = 0; + let keep = + Prim.Array_tabulate( + xs.size(), + func i { + if (f(xs[i])) { + count += 1; + true + } else { + false + } + } + ); + var nextKeep = 0; + Prim.Array_tabulate( + count, + func _ { + while (not keep[nextKeep]) { + nextKeep += 1; + }; + nextKeep += 1; + xs[nextKeep - 1]; + } + ) }; + /// Output array contains each transformed optional value; ordering retained. public func mapFilter(xs : [A], f : A -> ?B) : [B] { - let ys : Buffer.Buffer = Buffer.Buffer(xs.size()); - for (x in xs.vals()) { - switch (f(x)) { - case null {}; - case (?y) { ys.add(y) }; + var count = 0; + let options = + Prim.Array_tabulate( + xs.size(), + func i { + let result = f(xs[i]); + switch (result) { + case (?element) { + count += 1; + result + }; + case null { + null + } + } + } + ); + + var nextSome = 0; + Prim.Array_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 mapFilter" + } + } } - }; - ys.toArray(); + ) }; + /// Aggregate and transform values into a single output value, by increasing indices. public func foldLeft(xs : [A], initial : B, f : (B, A) -> B) : B { var acc = initial; @@ -296,5 +340,5 @@ module { xs[size - 1 - n]; }); }; - } + diff --git a/src/Buffer.mo b/src/Buffer.mo index c717378e..ebe3b667 100644 --- a/src/Buffer.mo +++ b/src/Buffer.mo @@ -1,166 +1,1758 @@ -/// Generic, extensible buffers +/// Class `Buffer` provides a mutable list of elements of type `X`. +/// The class wraps and resizes an underyling array that holds the elements, +/// and thus is comparable to ArrayLists or Vectors in other languages. /// -/// Generic, mutable sequences that grow to accommodate arbitrary numbers of elements. +/// When required, the current state of a buffer object can be converted to a fixed-size array of its elements. +/// This is recommended for example when storing a buffer to a stable variable. /// -/// Class `Buffer` provides extensible, mutable sequences of elements of type `X`. -/// that can be efficiently produced and consumed with imperative code. -/// A buffer object can be extended by a single element or the contents of another buffer object. +/// Throughout this documentation, two terms come up that can be confused: `size` +/// and `capacity`. `size` is the length of the list that the buffer represents. +/// `capacity` is the length of the underyling array that backs this list. +/// `capacity` >= `size` is an invariant for this class. +/// +/// Like arrays, elements in the buffer are ordered by indices from 0 to `size`-1. /// -/// When required, the current state of a buffer object can be converted to a fixed-size array of its elements. +/// WARNING: Certain operations are amortized O(1) time, such as `add`, but run +/// in worst case O(n) time. These worst case runtimes may exceed the cycles limit +/// per message if the size of the buffer is large enough. Grow these structures +/// with discretion. All amortized operations below also list the worst case runtime. +/// +/// Constructor: +/// The argument `initCapacity` determines the initial capacity of the array. +/// The underlying array grows by a factor of 1.5 when its current capacity is +/// exceeded. Further, when the size of the buffer shrinks to be less than 1/4th +/// of the capacity, the underyling array is shrunk by a factor of 2. +/// +/// Runtime: O(initCapacity) /// -/// Buffers complement Motoko's non-extensible array types -/// (arrays do not support efficient extension, because the size of an array is -/// determined at construction and cannot be changed). +/// Space: O(initCapacity) import Prim "mo:⛔"; +import Result "Result"; +import Order "Order"; +import Array "Array"; module { + type Order = Order.Order; - /// Create a stateful buffer class encapsulating a mutable array. - /// - /// The argument `initCapacity` determines its initial capacity. - /// The underlying mutable array grows by doubling when its current - /// capacity is exceeded. - public class Buffer(initCapacity : Nat) { - var count : Nat = 0; - var elems : [var X] = [var]; // initially empty; allocated upon first `add` - - /// Adds a single element to the buffer. - public func add(elem : X) { - if (count == elems.size()) { - let size = - if (count == 0) { - if (initCapacity > 0) { initCapacity } else { 1 } - } else { - 2 * elems.size() - }; - let elems2 = Prim.Array_init(size, elem); - var i = 0; - label l loop { - if (i >= count) break l; - elems2[i] := elems[i]; - i += 1; - }; - elems := elems2; + // The following constants are used to manage the capacity. + // The length of `elements` is increased by `INCREASE_FACTOR` when capacity is reached. + // The length of `elements` is decreased by `DECREASE_FACTOR` when capacity is strictly less than + // `DECREASE_THRESHOLD`. + + // INCREASE_FACTOR = INCREASE_FACTOR_NUME / INCREASE_FACTOR_DENOM (with floating point division) + // Keep INCREASE_FACTOR low to minimize cycle limit problem + private let INCREASE_FACTOR_NUME = 3; + private let INCREASE_FACTOR_DENOM = 2; + private let DECREASE_THRESHOLD = 4; // Don't decrease capacity too early to avoid thrashing + private let DECREASE_FACTOR = 2; + private let DEFAULT_CAPACITY = 8; + + private func newCapacity(oldCapacity : Nat) : Nat { + if (oldCapacity == 0) { + 1; + } else { + // calculates ceil(oldCapacity * INCREASE_FACTOR) without floats + ((oldCapacity * INCREASE_FACTOR_NUME) + INCREASE_FACTOR_DENOM - 1) / INCREASE_FACTOR_DENOM + }; + }; + + public class Buffer(initCapacity : Nat) = this { + var _size : Nat = 0; // avoid name clash with `size()` method + var elements : [var ?X] = Prim.Array_init(initCapacity, null); + + /// Returns the current number of elements in the buffer. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func size() : Nat = _size; + + /// Adds a single element to the end of the buffer, doubling + /// the size of the array if capacity is exceeded. + /// + /// Amortized Runtime: O(1), Worst Case Runtime: O(size) + /// + /// Amortized Space: O(1), Worst Case Space: O(size) + public func add(element : X) { + if (_size == elements.size()) { + reserve(newCapacity(elements.size())); }; - elems[count] := elem; - count += 1; + elements[_size] := ?element; + _size += 1; }; - /// Removes the item that was inserted last and returns it or `null` if no - /// elements had been added to the Buffer. + /// Returns the element at index `index`. Traps if `index >= size`. Indexing is zero-based. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func get(index : Nat) : X { + switch (elements[index]) { + case (?element) element; + case null Prim.trap("Buffer index out of bounds in get"); + }; + }; + + /// Returns the element at index `index` as an option. + /// Returns `null` when `index >= size`. Indexing is zero-based. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func getOpt(index : Nat) : ?X { + if (index < _size) { + elements[index]; + } else { + null; + }; + }; + + /// Overwrites the current element at `index` with `element`. Traps if + /// `index` >= size. Indexing is zero-based. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func put(index : Nat, element : X) { + if (index >= _size) { + Prim.trap "Buffer index out of bounds in put"; + }; + elements[index] := ?element; + }; + + /// Removes and returns the last item in the buffer or `null` if + /// the buffer is empty. + /// + /// Amortized Runtime: O(1), Worst Case Runtime: O(size) + /// + /// Amortized Space: O(1), Worst Case Space: O(size) public func removeLast() : ?X { - if (count == 0) { - null + if (_size == 0) { + return null; + }; + + _size -= 1; + let lastElement = elements[_size]; + elements[_size] := null; + + if (_size < elements.size() / DECREASE_THRESHOLD) { + reserve(elements.size() / DECREASE_FACTOR); + }; + + lastElement; + }; + + /// Removes and returns the element at `index` from the buffer. + /// All elements with index > `index` are shifted one position to the left. + /// This may cause a downsizing of the array. + /// + /// Traps if index >= size. + /// + /// WARNING: Repeated removal of elements using this method is ineffecient + /// and might be a sign that you should consider a different data-structure + /// for your use case. + /// + /// Runtime: O(size) + /// + /// Amortized Space: O(1), Worst Case Space: O(size) + public func remove(index : Nat) : X { + if (index >= _size) { + Prim.trap "Buffer index out of bounds in remove"; + }; + + let element = elements[index]; + + // copy elements to new array and shift over in one pass + if ((_size - 1) : Nat < elements.size() / DECREASE_THRESHOLD) { + let elements2 = Prim.Array_init(elements.size() / DECREASE_FACTOR, null); + + var i = 0; + var j = 0; + label l while (i < _size) { + if (i == index) { + i += 1; + continue l; + }; + + elements2[j] := elements[i]; + i += 1; + j += 1; + }; + elements := elements2; } else { - count -= 1; - ?elems[count] + // just shift over elements + var i = index; + while (i < (_size - 1 : Nat)) { + elements[i] := elements[i + 1]; + i += 1; + }; + elements[_size] := null; }; + + _size -= 1; + + switch (element) { + case (?element) { + element + }; + case null { + Prim.trap "Malformed buffer in remove" + } + } }; - /// Adds all elements in buffer `b` to this buffer. - public func append(b : Buffer) { - let i = b.vals(); - loop { - switch (i.next()) { - case null return; - case (?x) { add(x) }; + /// Resets the buffer. Capacity is set to 8. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func clear() { + _size := 0; + reserve(DEFAULT_CAPACITY); + }; + + /// Removes all elements from the buffer for which the predicate returns false. + /// The predicate is given both the index of the element and the element itself. + /// This may cause a downsizing of the array. + /// + /// Runtime: O(size) + /// + /// Amortized Space: O(1), Worst Case Space: O(size) + public func filterEntries(predicate : (Nat, X) -> Bool) { + var numRemoved = 0; + let keep = Prim.Array_tabulate( + _size, + func i { + switch (elements[i]) { + case (?element) { + if (predicate(i, element)) { + true; + } else { + numRemoved += 1; + false; + }; + }; + case null { + Prim.trap "Malformed buffer in filter()"; + }; + }; + }, + ); + + let capacity = elements.size(); + + var original = elements; + if ((_size - numRemoved : Nat) < capacity / DECREASE_THRESHOLD) { + elements := Prim.Array_init(capacity / DECREASE_FACTOR, null); + }; + + var i = 0; + var j = 0; + while (i < _size) { + if (keep[i]) { + elements[j] := original[i]; + i += 1; + j += 1; + } else { + i += 1; }; }; + + _size -= numRemoved; }; - /// Returns the current number of elements. - public func size() : Nat = - count; + /// Returns the capacity of the buffer (the length of the underlying array). + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func capacity() : Nat = elements.size(); - /// Resets the buffer. - public func clear() = - count := 0; + /// Changes the capacity to `capacity`. Traps if `capacity` < `size`. + /// + /// Runtime: O(capacity) + /// + /// Space: O(capacity) + public func reserve(capacity : Nat) { + if (capacity < _size) { + Prim.trap "capacity must be >= size in reserve"; + }; - /// Returns a copy of this buffer. - public func clone() : Buffer { - let c = Buffer(elems.size()); + let elements2 = Prim.Array_init(capacity, null); + + var i = 0; + while (i < _size) { + elements2[i] := elements[i]; + i += 1; + }; + elements := elements2; + }; + + /// Adds all elements in buffer `b` to this buffer. + /// + /// Amortized Runtime: O(size2), Worst Case Runtime: O(size1 + size2) + /// + /// Amortized Space: O(1), Worst Case Space: O(size1 + size2) + public func append(buffer2 : Buffer) { + let size2 = buffer2.size(); + // Make sure you only allocate a new array at most once + if (_size + size2 > elements.size()) { + // FIXME would be nice to have a tabulate for var arrays here + reserve(newCapacity(_size + size2)); + }; var i = 0; - label l loop { - if (i >= count) break l; - c.add(elems[i]); + while (i < size2) { + elements[_size + i] := buffer2.getOpt i; i += 1; }; - c + + _size += size2; + }; + + /// Inserts `element` at `index`, shifts all elements to the right of + /// `index` over by one index. Traps if `index` is greater than size. + /// + /// Runtime: O(size) + /// + /// Amortized Space: O(1), Worst Case Space: O(size) + public func insert(index : Nat, element : X) { + if (index > _size) { + Prim.trap "Buffer index out of bounds in insert"; + }; + let capacity = elements.size(); + + if (_size + 1 > capacity) { + let capacity = elements.size(); + let elements2 = Prim.Array_init(newCapacity capacity, null); + var i = 0; + while (i < _size + 1) { + if (i < index) { + elements2[i] := elements[i]; + } else if (i == index) { + elements2[i] := ?element; + } else { + elements2[i] := elements[i - 1]; + }; + + i += 1; + }; + elements := elements2; + } else { + var i : Nat = _size; + while (i > index) { + elements[i] := elements[i - 1]; + i -= 1; + }; + elements[index] := ?element; + }; + + _size += 1; + }; + + /// Inserts `buffer2` at `index`, and shifts all elements to the right of + /// `index` over by size2. Traps if `index` is greater than size. + /// + /// Runtime: O(size) + /// + /// Amortized Space: O(1), Worst Case Space: O(size1 + size2) + public func insertBuffer(index : Nat, buffer2 : Buffer) { + if (index > _size) { + Prim.trap "Buffer index out of bounds in insertBuffer"; + }; + + let size2 = buffer2.size(); + let capacity = elements.size(); + + // copy elements to new array and shift over in one pass + if (_size + size2 > capacity) { + let elements2 = Prim.Array_init(newCapacity(_size + size2), null); + var i = 0; + for (element in elements.vals()) { + if (i == index) { + i += size2; + }; + elements2[i] := element; + i += 1; + }; + + i := 0; + while (i < size2) { + elements2[i + index] := buffer2.getOpt(i); + i += 1; + }; + elements := elements2; + } // just insert + else { + var i = index; + while (i < index + size2) { + if (i < _size) { + elements[i + size2] := elements[i]; + }; + elements[i] := buffer2.getOpt(i - index); + + i += 1; + }; + }; + + _size += size2; + }; + + /// Sorts the elements in the buffer according to `compare`. + /// Sort is deterministic, stable, and in-place. + /// + /// Runtime: O(size * log(size)) + /// + /// Space: O(size) + public func sort(compare : (X, X) -> Order.Order) { + // Stable merge sort in a bottom-up iterative style + if (_size == 0) { + return; + }; + let scratchSpace = Prim.Array_init(_size, null); + + let sizeDec = _size - 1 : Nat; + var currSize = 1; // current size of the subarrays being merged + // when the current size == size, the array has been merged into a single sorted array + while (currSize < _size) { + var leftStart = 0; // selects the current left subarray being merged + while (leftStart < sizeDec) { + let mid : Nat = if (leftStart + currSize - 1 : Nat < sizeDec) { + leftStart + currSize - 1; + } else { sizeDec }; + let rightEnd : Nat = if (leftStart + (2 * currSize) - 1 : Nat < sizeDec) { + leftStart + (2 * currSize) - 1; + } else { sizeDec }; + + // Merge subarrays elements[leftStart...mid] and elements[mid+1...rightEnd] + var left = leftStart; + var right = mid + 1; + var nextSorted = leftStart; + while (left < mid + 1 and right < rightEnd + 1) { + let leftOpt = elements[left]; + let rightOpt = elements[right]; + switch (leftOpt, rightOpt) { + case (?leftElement, ?rightElement) { + switch (compare(leftElement, rightElement)) { + case (#less or #equal) { + scratchSpace[nextSorted] := leftOpt; + left += 1; + }; + case (#greater) { + scratchSpace[nextSorted] := rightOpt; + right += 1; + }; + }; + }; + case (_, _) { + // only sorting non-null items + Prim.trap "Malformed buffer in sort"; + }; + }; + nextSorted += 1; + }; + while (left < mid + 1) { + scratchSpace[nextSorted] := elements[left]; + nextSorted += 1; + left += 1; + }; + while (right < rightEnd + 1) { + scratchSpace[nextSorted] := elements[right]; + nextSorted += 1; + right += 1; + }; + + // Copy over merged elements + var i = leftStart; + while (i < rightEnd + 1) { + elements[i] := scratchSpace[i]; + i += 1; + }; + + leftStart += 2 * currSize; + }; + currSize *= 2; + }; }; - /// Returns an `Iter` over the elements of this buffer. + /// Returns an Iterator (`Iter`) over the elements of this buffer. + /// Iterator provides a single method `next()`, which returns + /// elements in order, or `null` when out of elements to iterate over. + /// + /// Runtime: O(1) + /// + /// Space: O(1) public func vals() : { next : () -> ?X } = object { - var pos = 0; + // FIXME either handle modification to underlying list + // or explicitly warn users in documentation + var nextIndex = 0; public func next() : ?X { - if (pos == count) { null } else { - let elem = ?elems[pos]; - pos += 1; - elem - } - } + if (nextIndex >= _size) { + return null; + }; + let nextElement = elements[nextIndex]; + nextIndex += 1; + nextElement; + }; + }; + + // FOLLOWING METHODS ARE DEPRECATED + + /// @deprecated Use static library function instead. + public func clone() : Buffer { + let newBuffer = Buffer(elements.size()); + for (element in vals()) { + newBuffer.add(element); + }; + newBuffer; }; - /// Creates a new array containing this buffer's elements. + /// @deprecated Use static library function instead. public func toArray() : [X] = - // immutable clone of array - Prim.Array_tabulate( - count, - func(x : Nat) : X { elems[x] } - ); + // immutable clone of array + Prim.Array_tabulate( + _size, + func(i : Nat) : X { get i }, + ); - /// Creates a mutable array containing this buffer's elements. + /// @deprecated Use static library function instead. public func toVarArray() : [var X] { - if (count == 0) { [var] } else { - let a = Prim.Array_init(count, elems[0]); + if (_size == 0) { [var] } else { + let newArray = Prim.Array_init(_size, get 0); var i = 0; - label l loop { - if (i >= count) break l; - a[i] := elems[i]; + for (element in vals()) { + newArray[i] := element; i += 1; }; - a - } + newArray; + }; }; + }; - /// Gets the `i`-th element of this buffer. Traps if `i >= count`. Indexing is zero-based. - public func get(i : Nat) : X { - assert(i < count); - elems[i] + /// Returns true iff the buffer is empty. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func isEmpty(buffer : Buffer) : Bool = buffer.size() == 0; + + /// Returns true iff `buffer` contains `element` with respect to equality + /// defined by `equal`. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func contains(buffer : Buffer, element : X, equal : (X, X) -> Bool) : Bool { + for (current in buffer.vals()) { + if (equal(current, element)) { + return true; + }; }; - /// Gets the `i`-th element of the buffer as an option. Returns `null` when `i >= count`. Indexing is zero-based. - public func getOpt(i : Nat) : ?X { - if (i < count) { - ?elems[i] - } - else { - null - } + false; + }; + + /// Returns a copy of `buffer`, with the same capacity. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func clone(buffer : Buffer) : Buffer { + let newBuffer = Buffer(buffer.capacity()); + for (element in buffer.vals()) { + newBuffer.add(element); + }; + newBuffer; + }; + + /// Finds the greatest element in `buffer` defined by `compare`. + /// Returns `null` if `buffer` is empty. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. + public func max(buffer : Buffer, compare : (X, X) -> Order) : ?X { + if (buffer.size() == 0) { + return null; + }; + + var maxSoFar = buffer.get(0); + for (current in buffer.vals()) { + switch (compare(current, maxSoFar)) { + case (#greater) { + maxSoFar := current; + }; + case _ {}; + }; + }; + + ?maxSoFar; + }; + + /// Finds the least element in `buffer` defined by `compare`. + /// Returns `null` if `buffer` is empty. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. + public func min(buffer : Buffer, compare : (X, X) -> Order) : ?X { + if (buffer.size() == 0) { + return null; + }; + + var minSoFar = buffer.get(0); + for (current in buffer.vals()) { + switch (compare(current, minSoFar)) { + case (#less) { + minSoFar := current; + }; + case _ {}; + }; + }; + + ?minSoFar; + }; + + /// Defines equality for two buffers, using `equal` to recursively compare elements in the + /// buffers. Returns true iff the two buffers are of the same size, and `equal` + /// evaluates to true for every pair of elements in the two buffers of the same + /// index. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func equal(buffer1 : Buffer, buffer2 : Buffer, equal : (X, X) -> Bool) : Bool { + let size1 = buffer1.size(); + + if (size1 != buffer2.size()) { + return false; + }; + + var i = 0; + while (i < size1) { + if (not equal(buffer1.get(i), buffer2.get(i))) { + return false; + }; + i += 1; + }; + + true; + }; + + /// Defines comparison for two buffers, using `compare` to recursively compare elements in the + /// buffers. Comparison is defined lexicographically. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. + public func compare(buffer1 : Buffer, buffer2 : Buffer, compare : (X, X) -> Order.Order) : Order.Order { + let size1 = buffer1.size(); + let size2 = buffer2.size(); + let minSize = if (size1 < size2) { size1 } else { size2 }; + + var i = 0; + while (i < minSize) { + switch (compare(buffer1.get(i), buffer2.get(i))) { + case (#less) { + return #less; + }; + case (#greater) { + return #greater; + }; + case _ {}; + }; + i += 1; + }; + + if (size1 < size2) { + #less; + } else if (size1 == size2) { + #equal; + } else { + #greater; + }; + }; + + /// Creates a textual representation of `buffer`, using `toText` to recursively + /// convert the elements into Text. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `toText` runs in O(1) time and space. + public func toText(buffer : Buffer, toText : X -> Text) : Text { + let size : Int = buffer.size(); + var i = 0; + var text = ""; + while (i < size - 1) { + text := text # toText(buffer.get(i)) # ", "; // Text implemented as rope + i += 1; + }; + if (size > 0) { + // avoid the trailing comma + text := text # toText(buffer.get(i)); + }; + + "[" # text # "]"; + }; + + /// Hashes `buffer` using `hash` to hash the underlying elements. + /// The deterministic hash function is a function of the elements in the Buffer, as well + /// as their ordering. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `hash` runs in O(1) time and space. + public func hash(buffer : Buffer, hash : X -> Nat32) : Nat32 { + let size = buffer.size(); + var i = 0; + var accHash : Nat32 = 0; + + while (i < size) { + accHash := Prim.intToNat32Wrap(i) ^ accHash ^ hash(buffer.get(i)); + i += 1; + }; + + accHash; + }; + + /// Finds the first index of `element` in `buffer` using equality of elements defined + /// by `equal`. Returns `null` if `element` is not found. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func indexOf(element : X, buffer : Buffer, equal : (X, X) -> Bool) : ?Nat { + let size = buffer.size(); + var i = 0; + while (i < size) { + if (equal(buffer.get(i), element)) { + return ?i; + }; + i += 1; + }; + + null; + }; + + /// Finds the last index of `element` in `buffer` using equality of elements defined + /// by `equal`. Returns `null` if `element` is not found. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func lastIndexOf(element : X, buffer : Buffer, equal : (X, X) -> Bool) : ?Nat { + let size = buffer.size(); + if (size == 0) { + return null; + }; + var i = size; + while (i >= 1) { + i -= 1; + if (equal(buffer.get(i), element)) { + return ?i; + }; + }; + + null; + }; + + /// Searches for `subBuffer` in `buffer`, and returns the starting index if it is found. + /// + /// Runtime: O(size of buffer + size of subBuffer) + /// + /// Space: O(size of subBuffer) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func indexOfBuffer(subBuffer : Buffer, buffer : Buffer, equal : (X, X) -> Bool) : ?Nat { + // Uses the KMP substring search algorithm + // Implementation from: https://www.educative.io/answers/what-is-the-knuth-morris-pratt-algorithm + let size = buffer.size(); + let subSize = subBuffer.size(); + if (subSize > size or subSize == 0) { + return null; + }; + + // precompute lps + let lps = Prim.Array_init(subSize, 0); + var i = 0; + var j = 1; + + while (j < subSize) { + if (equal(subBuffer.get(i), subBuffer.get(j))) { + i += 1; + lps[j] := i; + j += 1; + } else if (i == 0) { + lps[j] := 0; + j += 1; + } else { + i := lps[i - 1]; + }; }; - /// Overwrites the current value of the `i`-entry of this buffer with `elem`. Traps if the - /// index is out of bounds. Indexing is zero-based. - public func put(i : Nat, elem : X) { - elems[i] := elem; + // start search + i := 0; + j := 0; + let subSizeDec = subSize - 1 : Nat; // hoisting loop invariant + while (i < subSize and j < size) { + if (equal(subBuffer.get(i), buffer.get(j)) and i == subSizeDec) { + return ?(j - i); + } else if (equal(subBuffer.get(i), buffer.get(j))) { + i += 1; + j += 1; + } else { + if (i != 0) { + i := lps[i - 1]; + } else { + j += 1; + }; + }; }; + + null; }; - /// Creates a buffer from immutable array elements. - public func fromArray(elems : [X]) : Buffer { - let buff = Buffer(elems.size()); - for (elem in elems.vals()) { - buff.add(elem) + /// Similar to indexOf, but runs in logarithmic time. Assumes that `buffer` is sorted. + /// Behavior is undefined if `buffer` is not sorted. Uses `compare` to + /// perform the search. Returns an index of `element` if it is found. + /// + /// Runtime: O(log(size)) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. + public func binarySearch(element : X, buffer : Buffer, compare : (X, X) -> Order.Order) : ?Nat { + var low = 0; + var high = buffer.size(); + + while (low < high) { + let mid = (low + high) / 2; + let current = buffer.get(mid); + switch (compare(element, current)) { + case (#equal) { + return ?mid; + }; + case (#less) { + high := mid; + }; + case (#greater) { + low := mid + 1; + }; + }; }; - buff + + null; }; - /// Creates a buffer from the elements of a mutable array. - public func fromVarArray(elems : [var X]) : Buffer { - let buff = Buffer(elems.size()); - for (elem in elems.vals()) { - buff.add(elem) + /// Returns the sub-buffer of `buffer` starting at index `start` + /// of length `length`. Traps if `start` is out of bounds, or `start + length` + /// is greater than the size of `buffer`. + /// + /// Runtime: O(length) + /// + /// Space: O(length) + public func subBuffer(buffer : Buffer, start : Nat, length : Nat) : Buffer { + let size = buffer.size(); + let end = start + length; // exclusive + if (start >= size or end > size) { + Prim.trap "Buffer index out of bounds in subBuffer"; + }; + + let newBuffer = Buffer(newCapacity length); + + var i = start; + while (i < end) { + newBuffer.add(buffer.get(i)); + + i += 1; + }; + + newBuffer; + }; + + /// Checks if `subBuffer` is a sub-Buffer of `buffer`. Uses `equal` to + /// compare elements. + /// + /// Runtime: O(size of subBuffer + size of buffer) + /// + /// Space: O(size of subBuffer) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func isSubBufferOf(subBuffer : Buffer, buffer : Buffer, equal : (X, X) -> Bool) : Bool { + switch (indexOfBuffer(subBuffer, buffer, equal)) { + case null subBuffer.size() == 0; + case _ true; + }; + }; + + /// Checks if `subBuffer` is a strict subBuffer of `buffer`, i.e. `subBuffer` must be + /// strictly contained inside both the first and last indices of `buffer`. + /// Uses `equal` to compare elements. + /// + /// Runtime: O(size of subBuffer + size of buffer) + /// + /// Space: O(size of subBuffer) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func isStrictSubBufferOf(subBuffer : Buffer, buffer : Buffer, equal : (X, X) -> Bool) : Bool { + let subBufferSize = subBuffer.size(); + + switch (indexOfBuffer(subBuffer, buffer, equal)) { + case (?index) { + index != 0 and index != (buffer.size() - subBufferSize : Nat) // enforce strictness + }; + case null { + subBufferSize == 0 and subBufferSize != buffer.size(); + }; + }; + }; + + /// Returns the prefix of `buffer` of length `length`. Traps if `length` + /// is greater than the size of `buffer`. + /// + /// Runtime: O(length) + /// + /// Space: O(length) + public func prefix(buffer : Buffer, length : Nat) : Buffer { + let size = buffer.size(); + if (length > size) { + Prim.trap "Buffer index out of bounds in prefix"; + }; + + let newBuffer = Buffer(newCapacity length); + + var i = 0; + while (i < length) { + newBuffer.add(buffer.get(i)); + i += 1; + }; + + newBuffer; + }; + + /// Checks if `prefix` is a prefix of `buffer`. Uses `equal` to + /// compare elements. + /// + /// Runtime: O(size of prefix) + /// + /// Space: O(size of prefix) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func isPrefixOf(prefix : Buffer, buffer : Buffer, equal : (X, X) -> Bool) : Bool { + let sizePrefix = prefix.size(); + if (buffer.size() < sizePrefix) { + return false; + }; + + var i = 0; + while (i < sizePrefix) { + if (not equal(buffer.get(i), prefix.get(i))) { + return false; + }; + + i += 1; + }; + + return true; + }; + + /// Checks if `prefix` is a strict prefix of `buffer`. Uses `equal` to + /// compare elements. + /// + /// Runtime: O(size of prefix) + /// + /// Space: O(size of prefix) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func isStrictPrefixOf(prefix : Buffer, buffer : Buffer, equal : (X, X) -> Bool) : Bool { + if (buffer.size() <= prefix.size()) { + return false; + }; + isPrefixOf(prefix, buffer, equal) + }; + + /// Returns the suffix of `buffer` of length `length`. + /// Traps if `length`is greater than the size of `buffer`. + /// + /// Runtime: O(length) + /// + /// Space: O(length) + public func suffix(buffer : Buffer, length : Nat) : Buffer { + let size = buffer.size(); + + if (length > size) { + Prim.trap "Buffer index out of bounds in suffix"; + }; + + let newBuffer = Buffer(newCapacity length); + + var i = size - length : Nat; + while (i < size) { + newBuffer.add(buffer.get(i)); + + i += 1; + }; + + newBuffer; + }; + + /// Checks if `suffix` is a suffix of `buffer`. Uses `equal` to compare + /// elements. + /// + /// Runtime: O(length of suffix) + /// + /// Space: O(length of suffix) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func isSuffixOf(suffix : Buffer, buffer : Buffer, equal : (X, X) -> Bool) : Bool { + let suffixSize = suffix.size(); + let bufferSize = buffer.size(); + if (bufferSize < suffixSize) { + return false; + }; + + var i = bufferSize; + var j = suffixSize; + while (i >= 1 and j >= 1) { + i -= 1; + j -= 1; + if (not equal(buffer.get(i), suffix.get(j))) { + return false; + }; + }; + + return true; + }; + + /// Checks if `suffix` is a strict suffix of `buffer`. Uses `equal` to compare + /// elements. + /// + /// Runtime: O(length of suffix) + /// + /// Space: O(length of suffix) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func isStrictSuffixOf(suffix : Buffer, buffer : Buffer, equal : (X, X) -> Bool) : Bool { + if (buffer.size() <= suffix.size()) { + return false; + }; + isSuffixOf(suffix, buffer, equal) + }; + + /// Returns true iff every element in `buffer` satisfies `predicate`. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. + public func forAll(buffer : Buffer, predicate : X -> Bool) : Bool { + for (element in buffer.vals()) { + if (not predicate element) { + return false; + }; + }; + + true; + }; + + /// Returns true iff some element in `buffer` satisfies `predicate`. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. + public func forSome(buffer : Buffer, predicate : X -> Bool) : Bool { + for (element in buffer.vals()) { + if (predicate element) { + return true; + }; + }; + + false; + }; + + /// Returns true iff no element in `buffer` satisfies `predicate`. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. + public func forNone(buffer : Buffer, predicate : X -> Bool) : Bool { + for (element in buffer.vals()) { + if (predicate element) { + return false; + }; + }; + + true; + }; + + /// Creates an array containing elements from `buffer`. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func toArray(buffer : Buffer) : [X] = + // immutable clone of array + Prim.Array_tabulate( + buffer.size(), + func(i : Nat) : X { buffer.get(i) }, + ); + + /// Creates a mutable array containing elements from `buffer`. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func toVarArray(buffer : Buffer) : [var X] { + let size = buffer.size(); + if (size == 0) { [var] } else { + let newArray = Prim.Array_init(size, buffer.get(0)); + var i = 1; + while (i < size) { + newArray[i] := buffer.get(i); + i += 1; + }; + newArray; + }; + }; + + /// Creates a buffer containing elements from `array`. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func fromArray(array : [X]) : Buffer { + // When returning new buffer, if possible, set the capacity + // to the capacity of the old buffer. Otherwise, return them + // at 2/3 capacity (like in this case). Alternative is to + // calculate what the size would be if the elements were + // sequentially added using `add`. This current strategy (2/3) + // is the upper bound of that calculation (if the last element + // added caused a capacity increase). + let newBuffer = Buffer(newCapacity(array.size())); + + for (element in array.vals()) { + newBuffer.add(element); + }; + + newBuffer; + }; + + /// Creates a buffer containing elements from `array`. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func fromVarArray(array : [var X]) : Buffer { + let newBuffer = Buffer(newCapacity(array.size())); + + for (element in array.vals()) { + newBuffer.add(element); + }; + + newBuffer; + }; + + /// Creates a buffer containing elements from `iter`. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func fromIter(iter : { next : () -> ?X }) : Buffer { + let newBuffer = Buffer(DEFAULT_CAPACITY); // can't get size from `iter` + + for (element in iter) { + newBuffer.add(element); + }; + + newBuffer; + }; + + /// Reallocates the array underlying `buffer` such that capacity == size. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + public func trimToSize(buffer : Buffer) { + let size = buffer.size(); + if (size < buffer.capacity()) { + buffer.reserve(size); + }; + }; + + /// Creates a new buffer by applying `f` to each element in `buffer`. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func map(buffer : Buffer, f : X -> Y) : Buffer { + let newBuffer = Buffer(buffer.capacity()); + + for (element in buffer.vals()) { + newBuffer.add(f element); + }; + + newBuffer; + }; + + /// Applies `f` to each element in `buffer`. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func iterate(buffer : Buffer, f : X -> ()) { + for (element in buffer.vals()) { + f element; + }; + }; + + /// Applies `f` to each element in `buffer` and its index. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapEntries(buffer : Buffer, f : (Nat, X) -> Y) : Buffer { + let newBuffer = Buffer(buffer.capacity()); + + var i = 0; + let size = buffer.size(); + while (i < size) { + newBuffer.add(f(i, buffer.get(i))); + i += 1; + }; + + newBuffer; + }; + + /// Creates a new buffer by applying `f` to each element in `buffer`, + /// and keeping all non-null elements. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapFilter(buffer : Buffer, f : X -> ?Y) : Buffer { + let newBuffer = Buffer(buffer.capacity()); + + for (element in buffer.vals()) { + switch (f element) { + case (?element) { + newBuffer.add(element); + }; + case _ {}; + }; + }; + + newBuffer; + }; + + /// Creates a new buffer by applying `f` to each element in `buffer`. + /// If any invocation of `f` produces an `#err`, returns an `#err`. Otherwise + /// Returns an `#ok` containing the new buffer. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func mapResult(buffer : Buffer, f : X -> Result.Result) : Result.Result, E> { + let newBuffer = Buffer(buffer.capacity()); + + for (element in buffer.vals()) { + switch (f element) { + case (#ok result) { + newBuffer.add(result); + }; + case (#err e) { + return #err e; + }; + }; + }; + + #ok newBuffer; + }; + + /// Creates a new buffer by applying `k` to each element in `buffer`, + /// and concatenating the resulting buffers in order. This operation + /// is similar to what in other functional languages is known as monadic bind. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `f` runs in O(1) time and space. + public func chain(buffer : Buffer, k : X -> Buffer) : Buffer { + let newBuffer = Buffer(buffer.size() * 4); + + for (element in buffer.vals()) { + newBuffer.append(k element); + }; + + newBuffer; + }; + + /// Collapses the elements in `buffer` into a single value by starting with `base` + /// and progessively combining elements into `base` with `combine`. Iteration runs + /// left to right. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `combine` runs in O(1) time and space. + public func foldLeft(buffer : Buffer, base : A, combine : (A, X) -> A) : A { + var accumulation = base; + + for (element in buffer.vals()) { + accumulation := combine(accumulation, element); + }; + + accumulation; + }; + + /// Collapses the elements in `buffer` into a single value by starting with `base` + /// and progessively combining elements into `base` with `combine`. Iteration runs + /// right to left. + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `combine` runs in O(1) time and space. + public func foldRight(buffer : Buffer, base : A, combine : (X, A) -> A) : A { + let size = buffer.size(); + if (size == 0) { + return base; + }; + var accumulation = base; + + var i = size; + while (i >= 1) { + i -= 1; // to avoid Nat underflow, subtract first and stop iteration at 1 + accumulation := combine(buffer.get(i), accumulation); + }; + + accumulation; + }; + + /// Returns the first element of `buffer`. Traps if `buffer` is empty. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func first(buffer : Buffer) : X = buffer.get(0); + + /// Returns the last element of `buffer`. Traps if `buffer` is empty. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func last(buffer : Buffer) : X = buffer.get(buffer.size() - 1); + + /// Returns a new buffer with capacity and size 1, containing `element`. + /// + /// Runtime: O(1) + /// + /// Space: O(1) + public func make(element : X) : Buffer { + let newBuffer = Buffer(1); + newBuffer.add(element); + newBuffer; + }; + + /// Reverses the order of elements in `buffer`. + /// + /// Runtime: O(size) + /// + // Space: O(1) + public func reverse(buffer : Buffer) { + let size = buffer.size(); + if (size == 0) { + return; + }; + + var i = 0; + var j = size - 1 : Nat; + var temp = buffer.get(0); + while (i < size / 2) { + temp := buffer.get(j); + buffer.put(j, buffer.get(i)); + buffer.put(i, temp); + i += 1; + j -= 1; + }; + }; + + /// Merges two sorted buffers into a single sorted buffer, using `compare` to define + /// the ordering. The final ordering is stable. Behavior is undefined if either + /// `buffer1` or `buffer2` is not sorted. + /// + /// Runtime: O(size1 + size2) + /// + /// Space: O(size1 + size2) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. + public func merge(buffer1 : Buffer, buffer2 : Buffer, compare : (X, X) -> Order) : Buffer { + let size1 = buffer1.size(); + let size2 = buffer2.size(); + + let newBuffer = Buffer(newCapacity(size1 + size2)); + + var pointer1 = 0; + var pointer2 = 0; + + while (pointer1 < size1 and pointer2 < size2) { + let current1 = buffer1.get(pointer1); + let current2 = buffer2.get(pointer2); + + switch (compare(current1, current2)) { + case (#less) { + newBuffer.add(current1); + pointer1 += 1; + }; + case _ { + newBuffer.add(current2); + pointer2 += 1; + }; + }; + }; + + while (pointer1 < size1) { + newBuffer.add(buffer1.get(pointer1)); + pointer1 += 1; + }; + + while (pointer2 < size2) { + newBuffer.add(buffer2.get(pointer2)); + pointer2 += 1; + }; + + newBuffer; + }; + + /// Eliminates all duplicate elements in `buffer` as defined by `compare`. + /// Elimination is stable with respect to the original ordering of the elements. + /// + /// Runtime: O(size * log(size)) + /// + /// Space: O(size) + public func removeDuplicates(buffer : Buffer, compare : (X, X) -> Order) { + let size = buffer.size(); + let indices = Prim.Array_tabulate<(Nat, X)>(size, func i = (i, buffer.get(i))); + // Sort based on element, while carrying original index information + // This groups together the duplicate elements + let sorted = Array.sort<(Nat, X)>(indices, func(pair1, pair2) = compare(pair1.1, pair2.1)); + let uniques = Buffer<(Nat, X)>(size); + + // Iterate over elements + var i = 0; + while (i < size) { + var j = i; + // Iterate over duplicate elements, and find the smallest index among them (for stability) + var minIndex = sorted[j]; + label duplicates while (j < (size - 1 : Nat)) { + let pair1 = sorted[j]; + let pair2 = sorted[j + 1]; + switch (compare(pair1.1, pair2.1)) { + case (#equal) { + if (pair2.0 < pair1.0) { + minIndex := pair2; + }; + j += 1; + }; + case _ { + break duplicates; + }; + }; + }; + + uniques.add(minIndex); + i := j + 1; + }; + + // resort based on original ordering and place back in buffer + uniques.sort( + func(pair1, pair2) { + if (pair1.0 < pair2.0) { + #less; + } else if (pair1.0 == pair2.0) { + #equal; + } else { + #greater; + }; + }, + ); + + buffer.clear(); + buffer.reserve(uniques.size()); + for (element in uniques.vals()) { + buffer.add(element.1); + }; + }; + + /// Splits `buffer` into a pair of buffers where all elements in the left + /// buffer satisfy `predicate` and all elements in the right buffer do not. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. + public func partition(buffer : Buffer, predicate : X -> Bool) : (Buffer, Buffer) { + let size = buffer.size(); + let trueBuffer = Buffer(size); + let falseBuffer = Buffer(size); + + for (element in buffer.vals()) { + if (predicate element) { + trueBuffer.add(element); + } else { + falseBuffer.add(element); + }; + }; + + (trueBuffer, falseBuffer); + }; + + /// Splits the buffer into two buffers at `index`, where the left buffer contains + /// all elements with indices less than `index`, and the right buffer contains all + /// elements with indices greater than or equal to `index`. Traps if `index` is out + /// of bounds. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `compare` runs in O(1) time and space. + public func split(buffer : Buffer, index : Nat) : (Buffer, Buffer) { + let size = buffer.size(); + + if (index < 0 or index > size) { + Prim.trap "Index out of bounds in split"; + }; + + let buffer1 = Buffer(newCapacity index); + let buffer2 = Buffer(newCapacity(size - index)); + + var i = 0; + while (i < index) { + buffer1.add(buffer.get(i)); + i += 1; + }; + while (i < size) { + buffer2.add(buffer.get(i)); + i += 1; + }; + + (buffer1, buffer2); + }; + + /// Breaks up `buffer` into buffers of size `size`. The last chunk may + /// have less than `size` elements if the number of elements is not divisible + /// by the chunk size. + /// + /// Runtime: O(number of elements in buffer) + /// + /// Space: O(number of elements in buffer) + public func chunk(buffer : Buffer, size : Nat) : Buffer> { + if (size == 0) { + Prim.trap "Chunk size must be non-zero in chunk"; + }; + + // ceil(buffer.size() / size) + let newBuffer = Buffer>((buffer.size() + size - 1) / size); + + var newInnerBuffer = Buffer(newCapacity size); + var innerSize = 0; + for (element in buffer.vals()) { + if (innerSize == size) { + newBuffer.add(newInnerBuffer); + newInnerBuffer := Buffer(newCapacity size); + innerSize := 0; + }; + newInnerBuffer.add(element); + innerSize += 1; + }; + if (innerSize > 0) { + newBuffer.add(newInnerBuffer); + }; + + newBuffer; + }; + + /// Groups equal and adjacent elements in the list into sub lists. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func groupBy(buffer : Buffer, equal : (X, X) -> Bool) : Buffer> { + let size = buffer.size(); + let newBuffer = Buffer>(size); + if (size == 0) { + return newBuffer; + }; + + var i = 0; + var baseElement = buffer.get(0); + var newInnerBuffer = Buffer(size); + while (i < size) { + let element = buffer.get(i); + + if (equal(baseElement, element)) { + newInnerBuffer.add(element); + } else { + newBuffer.add(newInnerBuffer); + baseElement := element; + newInnerBuffer := Buffer(size - i); + newInnerBuffer.add(element); + }; + i += 1; + }; + if (newInnerBuffer.size() > 0) { + newBuffer.add(newInnerBuffer); + }; + + newBuffer; + }; + + /// Flattens the buffer of buffers into a single buffer. + /// + /// Runtime: O(number of elements in buffer) + /// + /// Space: O(number of elements in buffer) + public func flatten(buffer : Buffer>) : Buffer { + let size = buffer.size(); + if (size == 0) { + return Buffer(0); + }; + + let newBuffer = Buffer( + if (buffer.get(0).size() != 0) { + newCapacity(buffer.get(0).size() * size); + } else { + newCapacity(size); + }, + ); + + for (innerBuffer in buffer.vals()) { + for (innerElement in innerBuffer.vals()) { + newBuffer.add(innerElement); + }; + }; + + newBuffer; + }; + + /// Combines the two buffers into a single buffer of pairs, pairing together + /// elements with the same index. If one buffer is longer than the other, the + /// remaining elements from the longer buffer are not included. + /// + /// Runtime: O(min(size1, size2)) + /// + /// Space: O(min(size1, size2)) + public func zip(buffer1 : Buffer, buffer2 : Buffer) : Buffer<(X, Y)> { + // compiler should pull lamda out as a static function since it is fully closed + zipWith(buffer1, buffer2, func(x, y) = (x, y)); + }; + + /// Combines the two buffers into a single buffer, pairing together + /// elements with the same index and combining them using `zip`. If + /// one buffer is longer than the other, the remaining elements from + /// the longer buffer are not included. + /// + /// Runtime: O(min(size1, size2)) + /// + /// Space: O(min(size1, size2)) + /// + /// *Runtime and space assumes that `zip` runs in O(1) time and space. + public func zipWith(buffer1 : Buffer, buffer2 : Buffer, zip : (X, Y) -> Z) : Buffer { + let size1 = buffer1.size(); + let size2 = buffer2.size(); + let minSize = if (size1 < size2) { size1 } else { size2 }; + + var i = 0; + let newBuffer = Buffer(newCapacity minSize); + while (i < minSize) { + newBuffer.add(zip(buffer1.get(i), buffer2.get(i))); + i += 1; + }; + newBuffer; + }; + + /// Creates a new buffer taking elements in order from `buffer` until predicate + /// returns false. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. + public func takeWhile(buffer : Buffer, predicate : X -> Bool) : Buffer { + let newBuffer = Buffer(buffer.size()); + + for (element in buffer.vals()) { + if (not predicate element) { + return newBuffer; + }; + newBuffer.add(element); + }; + + newBuffer; + }; + + /// Creates a new buffer excluding elements in order from `buffer` until predicate + /// returns false. + /// + /// Runtime: O(size) + /// + /// Space: O(size) + /// + /// *Runtime and space assumes that `predicate` runs in O(1) time and space. + public func dropWhile(buffer : Buffer, predicate : X -> Bool) : Buffer { + let size = buffer.size(); + let newBuffer = Buffer(size); + + var i = 0; + var take = false; + label iter for (element in buffer.vals()) { + if (not (take or predicate element)) { + take := true; + }; + if (take) { + newBuffer.add(element); + }; }; - buff + newBuffer; }; -} +}; diff --git a/src/Iter.mo b/src/Iter.mo index 4c0ebe2f..0665db14 100644 --- a/src/Iter.mo +++ b/src/Iter.mo @@ -193,7 +193,7 @@ module { public func toArray(xs : Iter) : [A] { let buffer = Buffer.Buffer(8); iterate(xs, func(x : A, ix : Nat) { buffer.add(x) }); - return buffer.toArray() + return Buffer.toArray(buffer) }; /// Like `toArray` but for Arrays with mutable elements. diff --git a/test/arrayTest.mo b/test/arrayTest.mo index 7690eef0..7a4de7f6 100644 --- a/test/arrayTest.mo +++ b/test/arrayTest.mo @@ -143,6 +143,14 @@ let suite = Suite.suite("Array", [ }, M.equals(T.array(T.natTestable, [ 2, 4, 6 ])) ), + Suite.test( + "filter empty", + do { + let isEven = func (x : Nat) : Bool { x % 2 == 0 }; + Array.filter([] : [Nat], isEven); + }, + M.equals(T.array(T.natTestable, [] : [Nat])) + ), Suite.test( "mapFilter", do { @@ -151,6 +159,14 @@ let suite = Suite.suite("Array", [ }, M.equals(T.array(T.natTestable, [ 2, 4, 6 ])) ), + Suite.test( + "mapFilter empty", + do { + let isEven = func (x : Nat) : ?Nat { if (x % 2 == 0) ?x else null }; + Array.mapFilter([] : [Nat], isEven); + }, + M.equals(T.array(T.natTestable, [] : [Nat])) + ), findTest, Suite.test( "foldLeft", diff --git a/test/bufTest.mo b/test/bufTest.mo index e6468dd4..852a1f77 100644 --- a/test/bufTest.mo +++ b/test/bufTest.mo @@ -1,113 +1,3236 @@ import Prim "mo:⛔"; import B "mo:base/Buffer"; -import I "mo:base/Iter"; -import O "mo:base/Option"; +import Iter "mo:base/Iter"; +import Option "mo:base/Option"; +import Nat "mo:base/Nat"; +import Hash "mo:base/Hash"; +import Nat32 "mo:base/Nat32"; +import Order "mo:base/Order"; import Suite "mo:matchers/Suite"; import T "mo:matchers/Testable"; import M "mo:matchers/Matchers"; -// test repeated growing -let a = B.Buffer(3); -for (i in I.range(0, 123)) { - a.add(i); +let {run;test;suite} = Suite; + +let NatBufferTestable : T.Testable> = object { + public func display(buffer : B.Buffer) : Text { + B.toText(buffer, Nat.toText); + }; + public func equals(buffer1 : B.Buffer, buffer2 : B.Buffer) : Bool { + B.equal(buffer1, buffer2, Nat.equal) + }; +}; + +class OrderTestable(initItem : Order.Order) : T.TestableItem { + public let item = initItem; + public func display(order : Order.Order) : Text { + switch (order) { + case (#less) { + "#less" + }; + case (#greater) { + "#greater" + }; + case (#equal) { + "#equal" + } + } + }; + public let equals = Order.equal; +}; + +/* --------------------------------------- */ +run(suite("construct", +[ + test( + "initial size", + B.Buffer(10).size(), + M.equals(T.nat(0)) + ), + test( + "initial capacity", + B.Buffer(10).capacity(), + M.equals(T.nat(10)) + ), +])); + +/* --------------------------------------- */ + +var buffer = B.Buffer(10); +for (i in Iter.range(0, 3)) { + buffer.add(i); +}; + +run(suite("add", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(4)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(10)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(2); +for (i in Iter.range(0, 3)) { + buffer.add(i); +}; + +run(suite("add with capacity change", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(4)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(5)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(0); +for (i in Iter.range(0, 3)) { + buffer.add(i); +}; + +run(suite("add with capacity change, initial capacity 0", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(4)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(5)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(2); + +run(suite("removeLast on empty buffer", +[ + test( + "return value", + buffer.removeLast(), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "size", + buffer.size(), + M.equals(T.nat(0)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(2)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(2); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +run(suite("removeLast", +[ + test( + "return value", + buffer.removeLast(), + M.equals(T.optional(T.natTestable, ?5)) + ), + test( + "size", + buffer.size(), + M.equals(T.nat(5)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +for (i in Iter.range(0, 5)) { + ignore buffer.removeLast(); +}; + +run(suite("removeLast until empty", +[ + test( + "return value", + buffer.removeLast(), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "size", + buffer.size(), + M.equals(T.nat(0)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(2)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(0); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +run(suite("remove", +[ + test( + "return value", + buffer.remove(2), + M.equals(T.nat(2)) + ), + test( + "size", + buffer.size(), + M.equals(T.nat(5)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 3, 4, 5])) + ), + test( + "return value", + buffer.remove(0), + M.equals(T.nat(0)) + ), + test( + "size", + buffer.size(), + M.equals(T.nat(4)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [1, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(0); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +for (i in Iter.range(0, 5)) { + ignore buffer.remove(5 - i); +}; + +run(suite("remove until empty", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(0)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(2)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(1); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer.filterEntries(func(_, x) = x % 2 == 0); + +run(suite("filterEntries", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(3)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 2, 4])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(1); +buffer.filterEntries(func(_, x) = x % 2 == 0); + +run(suite("filterEntries on empty", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(0)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(12); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; +buffer.filterEntries(func(i, x) = i + x == 2); + +run(suite("filterEntries size down", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(1)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [1])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(10); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +run(suite("get and getOpt", +[ + test( + "get", + buffer.get(2), + M.equals(T.nat(2)) + ), + test( + "getOpt success", + buffer.getOpt(0), + M.equals(T.optional(T.natTestable, ?0)) + ), + test( + "getOpt out of bounds", + buffer.getOpt(10), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(10); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer.put(2, 20); + +run(suite("put", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(6)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 20, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(10); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + + +buffer.reserve(6); + +run(suite("decrease capacity", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(6)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(10); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + + +buffer.reserve(20); + +run(suite("increase capacity", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(6)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(20)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(10); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +var buffer2 = B.Buffer(20); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.append(buffer2); + +run(suite("append", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(12)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(18)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(10); +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(0); + +buffer.append(buffer2); + +run(suite("append empty buffer", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(6)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(10)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(10); + +buffer2 := B.Buffer(10); + +for (i in Iter.range(0, 5)) { + buffer2.add(i); +}; + +buffer.append(buffer2); + +run(suite("append to empty buffer", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(6)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(10)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(8); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer.insert(3, 30); + +run(suite("insert", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(7)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 30, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(8); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer.insert(6, 60); + +run(suite("insert at back", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(7)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5, 60])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(8); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer.insert(0, 10); + +run(suite("insert at front", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(7)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [10, 0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(6); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer.insert(3, 30); + +run(suite("insert with capacity change", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(7)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(9)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 30, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(5); + +buffer.insert(0, 0); + +run(suite("insert into empty buffer", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(1)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(5)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(0); + +buffer.insert(0, 0); + +run(suite("insert into empty buffer with capacity change", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(1)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(15); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(3, buffer2); + +run(suite("insertBuffer", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(12)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(15)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 10, 11, 12, 13, 14, 15, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(15); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(0, buffer2); + +run(suite("insertBuffer at start", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(12)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(15)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(15); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(6, buffer2); + +run(suite("insertBuffer at end", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(12)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(15)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(8); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(3, buffer2); + +run(suite("insertBuffer with capacity change", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(12)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(18)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 10, 11, 12, 13, 14, 15, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(8); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(0, buffer2); + +run(suite("insertBuffer at start with capacity change", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(12)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(18)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(8); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(6, buffer2); + +run(suite("insertBuffer at end with capacity change", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(12)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(18)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(7); + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(0, buffer2); + +run(suite("insertBuffer to empty buffer", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(6)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(7)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [10, 11, 12, 13, 14, 15])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +buffer2 := B.Buffer(10); + +for (i in Iter.range(10, 15)) { + buffer2.add(i); +}; + +buffer.insertBuffer(0, buffer2); + +run(suite("insertBuffer to empty buffer with capacity change", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(6)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(9)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [10, 11, 12, 13, 14, 15])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +buffer.clear(); + +run(suite("clear", +[ + test( + "size", + buffer.size(), + M.equals(T.nat(0)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +buffer2 := B.clone(buffer); + +run(suite("clone", +[ + test( + "size", + buffer2.size(), + M.equals(T.nat(buffer.size())) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(buffer2.capacity())) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, B.toArray(buffer2))) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +var size = 0; + +for (element in buffer.vals()) { + M.assertThat(element, M.equals(T.nat(size))); + size += 1; +}; + +run(suite("vals", +[ + test( + "size", + size, + M.equals(T.nat(7)) + ) +])); + +/* --------------------------------------- */ +run(suite("array round trips", +[ + test( + "fromArray and toArray", + B.toArray(B.fromArray([0, 1, 2, 3])), + M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + ), + test( + "fromVarArray", + B.toArray(B.fromVarArray([var 0, 1, 2, 3])), + M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + ) +])); + +/* --------------------------------------- */ +run(suite("empty array round trips", +[ + test( + "fromArray and toArray", + B.toArray(B.fromArray([])), + M.equals(T.array(T.natTestable, [])) + ), + test( + "fromVarArray", + B.toArray(B.fromVarArray([var])), + M.equals(T.array(T.natTestable, [])) + ) +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 6)) { + buffer.add(i) +}; + +run(suite("iter round trips", +[ + test( + "fromIter and vals", + B.toArray(B.fromIter(buffer.vals())), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5, 6])) + ), + test( + "empty", + B.toArray(B.fromIter(B.Buffer(2).vals())), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +B.trimToSize(buffer); + +run(suite("trimToSize", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(7)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5, 6])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +B.trimToSize(buffer); + +run(suite("trimToSize on empty", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(0)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +buffer2 := B.map(buffer, func x = x * 2); + +run(suite("map", +[ + test( + "capacity", + buffer2.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [0, 2, 4, 6, 8, 10, 12])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(0); + +buffer2 := B.map(buffer, func x = x * 2); + +run(suite("map empty", +[ + test( + "capacity", + buffer2.capacity(), + M.equals(T.nat(0)) + ), + test( + "elements", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +var sum = 0; + +B.iterate(buffer, func x = sum += x); + +run(suite("iterate", +[ + test( + "sum", + sum, + M.equals(T.nat(21)) + ), + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5, 6])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +buffer2 := B.chain(buffer, func x = B.make x); + +run(suite("chain", +[ + test( + "elements", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, B.toArray(buffer))) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +buffer2 := B.mapFilter(buffer, func x = if (x % 2 == 0) { ?x } else { null }); + +run(suite("mapFilter", +[ + test( + "capacity", + buffer2.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [0, 2, 4, 6])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +buffer2 := B.mapEntries(buffer, func (i, x) = i * x); + +run(suite("mapEntries", +[ + test( + "capacity", + buffer2.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [0, 1, 4, 9, 16, 25, 36])) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +var bufferResult = B.mapResult(buffer, func x = #ok x); + +run(suite("mapResult success", +[ + test( + "return value", + #ok buffer, + M.equals(T.result, Text>(NatBufferTestable, T.textTestable, bufferResult)) + ) +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +bufferResult := + B.mapResult( + buffer, + func x = if (x == 4) { #err "error"} else { #ok x } + ); + +run(suite("mapResult failure", +[ + test( + "return value", + #err "error", + M.equals(T.result, Text>(NatBufferTestable, T.textTestable, bufferResult)) + ) +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +run(suite("foldLeft", +[ + test( + "return value", + B.foldLeft(buffer, "", func(acc, x) = acc # Nat.toText(x)), + M.equals(T.text("0123456")) + ), + test( + "return value empty", + B.foldLeft(B.Buffer(4), "", func(acc, x) = acc # Nat.toText(x)), + M.equals(T.text("")) + ) +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +run(suite("foldRight", +[ + test( + "return value", + B.foldRight(buffer, "", func(x, acc) = acc # Nat.toText(x)), + M.equals(T.text("6543210")) + ), + test( + "return value empty", + B.foldRight(B.Buffer(4), "", func(x, acc) = acc # Nat.toText(x)), + M.equals(T.text("")) + ) +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 6)) { + buffer.add(i); +}; + +run(suite("forAll", +[ + test( + "true", + B.forAll(buffer, func x = x >= 0), + M.equals(T.bool(true)) + ), + test( + "false", + B.forAll(buffer, func x = x % 2 == 0), + M.equals(T.bool(false)) + ), + test( + "default", + B.forAll(B.Buffer(2), func _ = false), + M.equals(T.bool(true)) + ) +])); + +/* --------------------------------------- */ +run(suite("forSome", +[ + test( + "true", + B.forSome(buffer, func x = x % 2 == 0), + M.equals(T.bool(true)) + ), + test( + "false", + B.forSome(buffer, func x = x < 0), + M.equals(T.bool(false)) + ), + test( + "default", + B.forSome(B.Buffer(2), func _ = false), + M.equals(T.bool(false)) + ) +])); + +/* --------------------------------------- */ +run(suite("forNone", +[ + test( + "true", + B.forNone(buffer, func x = x < 0), + M.equals(T.bool(true)) + ), + test( + "false", + B.forNone(buffer, func x = x % 2 != 0), + M.equals(T.bool(false)) + ), + test( + "default", + B.forNone(B.Buffer(2), func _ = true), + M.equals(T.bool(true)) + ) +])); + +/* --------------------------------------- */ + +buffer := B.make(1); + +run(suite("make", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [1])) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); + +for (i in Iter.range(0, 5)) { + buffer.add(i) +}; + +run(suite("contains", +[ + test( + "true", + B.contains(buffer, 2, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "true", + B.contains(buffer, 9, Nat.equal), + M.equals(T.bool(false)) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); + +run(suite("contains empty", +[ + test( + "true", + B.contains(buffer, 2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "true", + B.contains(buffer, 9, Nat.equal), + M.equals(T.bool(false)) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); + +buffer.add(2); +buffer.add(1); +buffer.add(10); +buffer.add(1); +buffer.add(0); +buffer.add(3); + +run(suite("max", +[ + test( + "return value", + B.max(buffer, Nat.compare), + M.equals(T.optional(T.natTestable, ?10)) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); + +buffer.add(2); +buffer.add(1); +buffer.add(10); +buffer.add(1); +buffer.add(0); +buffer.add(3); +buffer.add(0); + +run(suite("min", +[ + test( + "return value", + B.min(buffer, Nat.compare), + M.equals(T.optional(T.natTestable, ?0)) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); +buffer.add(2); + +run(suite("isEmpty", +[ + test( + "true", + B.isEmpty(B.Buffer(2)), + M.equals(T.bool(true)) + ), + test( + "false", + B.isEmpty(buffer), + M.equals(T.bool(false)) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); + +buffer.add(2); +buffer.add(1); +buffer.add(10); +buffer.add(1); +buffer.add(0); +buffer.add(3); +buffer.add(0); + +B.removeDuplicates(buffer, Nat.compare); + +run(suite("removeDuplicates", +[ + test( + "elements (stable ordering)", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [2, 1, 10, 0, 3])) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); + +B.removeDuplicates(buffer, Nat.compare); + +run(suite("removeDuplicates empty", +[ + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [] : [Nat])) + ) +])); + +/* --------------------------------------- */ + +buffer := B.Buffer(3); + +for (i in Iter.range(0, 4)) { + buffer.add(2); +}; + +B.removeDuplicates(buffer, Nat.compare); + +run(suite("removeDuplicates repeat singleton", +[ + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [2])) + ) +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +run(suite("hash", +[ + test( + "empty buffer", + Nat32.toNat(B.hash(B.Buffer(8), Hash.hash)), + M.equals(T.nat(0)) + ), + test( + "non-empty buffer", + Nat32.toNat(B.hash(buffer, Hash.hash)), + M.equals(T.nat(3365238326)) + ) +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +run(suite("toText", +[ + test( + "empty buffer", + B.toText(B.Buffer(3), Nat.toText), + M.equals(T.text("[]")) + ), + test( + "singleton buffer", + B.toText(B.make(3), Nat.toText), + M.equals(T.text("[3]")) + ), + test( + "non-empty buffer", + B.toText(buffer, Nat.toText), + M.equals(T.text("[0, 1, 2, 3, 4, 5]")) + ) +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(3); + +for (i in Iter.range(0, 2)) { + buffer.add(i); +}; + +run(suite("equal", +[ + test( + "empty buffers", + B.equal(B.Buffer(3), B.Buffer(2), Nat.equal), + M.equals(T.bool(true)) + ), + test( + "non-empty buffers", + B.equal(buffer, B.clone(buffer), Nat.equal), + M.equals(T.bool(true)) + ), + test( + "non-empty and empty buffers", + B.equal(buffer, B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), + test( + "non-empty buffers mismatching lengths", + B.equal(buffer, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), +])); + +/* --------------------------------------- */ +buffer := B.Buffer(3); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer2 := B.Buffer(3); + +for (i in Iter.range(0, 2)) { + buffer.add(i); +}; + +var buffer3 = B.Buffer(3); + +for (i in Iter.range(2, 5)) { + buffer3.add(i); +}; + +run(suite("compare", +[ + test( + "empty buffers", + B.compare(B.Buffer(3), B.Buffer(2), Nat.compare), + M.equals(OrderTestable(#equal)) + ), + test( + "non-empty buffers equal", + B.compare(buffer, B.clone(buffer), Nat.compare), + M.equals(OrderTestable(#equal)) + ), + test( + "non-empty and empty buffers", + B.compare(buffer, B.Buffer(3), Nat.compare), + M.equals(OrderTestable(#greater)) + ), + test( + "non-empty buffers mismatching lengths", + B.compare(buffer, buffer2, Nat.compare), + M.equals(OrderTestable(#greater)) + ), + test( + "non-empty buffers lexicographic difference", + B.compare(buffer, buffer3, Nat.compare), + M.equals(OrderTestable(#less)) + ), +])); + +/* --------------------------------------- */ + +var nestedBuffer = B.Buffer>(3); +for (i in Iter.range(0, 4)) { + let innerBuffer = B.Buffer(2); + for (j in if (i % 2 == 0) { Iter.range(0, 4) } else { Iter.range(0, 3) }) { + innerBuffer.add(j) + }; + nestedBuffer.add(innerBuffer) +}; +nestedBuffer.add(B.Buffer(2)); + +buffer := B.flatten(nestedBuffer); + +run(suite("flatten", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(45)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 3, 4])) + ), +])); + +/* --------------------------------------- */ + +nestedBuffer := B.Buffer>(3); +for (i in Iter.range(0, 4)) { + nestedBuffer.add(B.Buffer(2)); +}; + +buffer := B.flatten(nestedBuffer); + +run(suite("flatten all empty inner buffers", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ + +nestedBuffer := B.Buffer>(3); +buffer := B.flatten(nestedBuffer); + +run(suite("flatten empty outer buffer", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(0)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 7)) { + buffer.add(i); +}; + +buffer2.clear(); + +for (i in Iter.range(0, 6)) { + buffer2.add(i); +}; + +buffer3.clear(); + +var buffer4 = B.make(3); + +B.reverse(buffer); +B.reverse(buffer2); +B.reverse(buffer3); +B.reverse(buffer4); + +run(suite("reverse", +[ + test( + "even elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [7, 6, 5, 4, 3, 2, 1, 0])) + ), + test( + "odd elements", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [6, 5, 4, 3, 2, 1, 0])) + ), + test( + "empty", + B.toArray(buffer3), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "singleton", + B.toArray(buffer4), + M.equals(T.array(T.natTestable, [3])) + ), +])); + + +/* --------------------------------------- */ + +buffer.clear(); +for (i in Iter.range(0, 5)) { + buffer.add(i) +}; + +var partition = B.partition(buffer, func x = x % 2 == 0); +buffer2 := partition.0; +buffer3 := partition.1; + +run(suite("partition", +[ + test( + "capacity of true buffer", + buffer2.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements of true buffer", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [0, 2, 4])) + ), + test( + "capacity of false buffer", + buffer3.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements of false buffer", + B.toArray(buffer3), + M.equals(T.array(T.natTestable, [1, 3, 5])) + ), +])); + +/* --------------------------------------- */ + +buffer.clear(); +for (i in Iter.range(0, 3)) { + buffer.add(i) +}; + +for (i in Iter.range(10, 13)) { + buffer.add(i) +}; + +buffer2.clear(); +for (i in Iter.range(2, 5)) { + buffer2.add(i) +}; +for (i in Iter.range(13, 15)) { + buffer2.add(i) +}; + +buffer := B.merge(buffer, buffer2, Nat.compare); + +run(suite("merge", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(23)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 2, 3, 3, 4, 5, 10, 11, 12, 13, 13, 14, 15])) + ), +])); + +/* --------------------------------------- */ + +buffer.clear(); +for (i in Iter.range(0, 3)) { + buffer.add(i) +}; + +buffer2.clear(); + +buffer := B.merge(buffer, buffer2, Nat.compare); + +run(suite("merge with empty", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + ), +])); + +/* --------------------------------------- */ + +buffer.clear(); +buffer2.clear(); + +buffer := B.merge(buffer, buffer2, Nat.compare); + +run(suite("merge two empty", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +buffer.add(0); +buffer.add(2); +buffer.add(1); +buffer.add(1); +buffer.add(5); +buffer.add(4); + +buffer.sort(Nat.compare); + +run(suite("sort even", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 1, 2, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +buffer.add(0); +buffer.add(2); +buffer.add(1); +buffer.add(1); +buffer.add(5); + +buffer.sort(Nat.compare); + +run(suite("sort odd", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 1, 2, 5])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +buffer.sort(Nat.compare); + +run(suite("sort empty", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); +buffer.add(2); + +buffer.sort(Nat.compare); + +run(suite("sort singleton", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [2] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +partition := B.split(buffer, 2); +buffer2 := partition.0; +buffer3 := partition.1; + +run(suite("split", +[ + test( + "capacity prefix", + buffer2.capacity(), + M.equals(T.nat(3)) + ), + test( + "elements prefix", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [0, 1])) + ), + test( + "capacity suffix", + buffer3.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements suffix", + B.toArray(buffer3), + M.equals(T.array(T.natTestable, [2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +partition := B.split(buffer, 0); +buffer2 := partition.0; +buffer3 := partition.1; + +run(suite("split at index 0", +[ + test( + "capacity prefix", + buffer2.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements prefix", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "capacity suffix", + buffer3.capacity(), + M.equals(T.nat(9)) + ), + test( + "elements suffix", + B.toArray(buffer3), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +partition := B.split(buffer, 6); +buffer2 := partition.0; +buffer3 := partition.1; + +run(suite("split at last index", +[ + test( + "capacity prefix", + buffer2.capacity(), + M.equals(T.nat(9)) + ), + test( + "elements prefix", + B.toArray(buffer2), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4, 5])) + ), + test( + "capacity suffix", + buffer3.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements suffix", + B.toArray(buffer3), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); +buffer2.clear(); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; +for (i in Iter.range(0, 3)) { + buffer2.add(i); +}; + +var bufferPairs = B.zip(buffer, buffer2); + +run(suite("zip", +[ + test( + "capacity", + bufferPairs.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements", + B.toArray(bufferPairs), + M.equals(T.array(T.tuple2Testable(T.natTestable, T.natTestable), + [(0, 0), (1, 1), (2, 2), (3, 3)])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); +buffer2.clear(); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +bufferPairs := B.zip(buffer, buffer2); + +run(suite("zip empty", +[ + test( + "capacity", + bufferPairs.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(bufferPairs), + M.equals(T.array(T.tuple2Testable(T.natTestable, T.natTestable), + [] : [(Nat, Nat)])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); +buffer2.clear(); + +bufferPairs := B.zip(buffer, buffer2); + +run(suite("zip both empty", +[ + test( + "capacity", + bufferPairs.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(bufferPairs), + M.equals(T.array(T.tuple2Testable(T.natTestable, T.natTestable), + [] : [(Nat, Nat)])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); +buffer2.clear(); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; +for (i in Iter.range(0, 3)) { + buffer2.add(i); +}; + +buffer3 := B.zipWith(buffer, buffer2, Nat.add); + +run(suite("zipWith", +[ + test( + "capacity", + buffer3.capacity(), + M.equals(T.nat(6)) + ), + test( + "elements", + B.toArray(buffer3), + M.equals(T.array(T.natTestable, [0, 2, 4, 6])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); +buffer2.clear(); + +for (i in Iter.range(0, 5)) { + buffer.add(i); +}; + +buffer3 := B.zipWith(buffer, buffer2, Nat.add); + +run(suite("zipWithEmpty", +[ + test( + "capacity", + buffer3.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(buffer3), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 8)) { + buffer.add(i); +}; + +var chunks = B.chunk(buffer, 2); + +run(suite("chunk", +[ + test( + "num chunks", + chunks.size(), + M.equals(T.nat(5)) + ), + test( + "chunk 0 capacity", + chunks.get(0).capacity(), + M.equals(T.nat(3)) + ), + test( + "chunk 0 elements", + B.toArray(chunks.get(0)), + M.equals(T.array(T.natTestable, [0, 1])) + ), + test( + "chunk 2 capacity", + chunks.get(2).capacity(), + M.equals(T.nat(3)) + ), + test( + "chunk 2 elements", + B.toArray(chunks.get(2)), + M.equals(T.array(T.natTestable, [4, 5])) + ), + test( + "chunk 4 capacity", + chunks.get(4).capacity(), + M.equals(T.nat(3)) + ), + test( + "chunk 4 elements", + B.toArray(chunks.get(4)), + M.equals(T.array(T.natTestable, [8])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +chunks := B.chunk(buffer, 3); + +run(suite("chunk empty", +[ + test( + "num chunks", + chunks.size(), + M.equals(T.nat(0)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +chunks := B.chunk(buffer, 10); + +run(suite("chunk larger than buffer", +[ + test( + "num chunks", + chunks.size(), + M.equals(T.nat(1)) + ), + test( + "chunk 0 elements", + B.toArray(chunks.get(0)), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +buffer.add(2); +buffer.add(2); +buffer.add(2); +buffer.add(1); +buffer.add(0); +buffer.add(0); +buffer.add(2); +buffer.add(1); +buffer.add(1); + +var groups = B.groupBy(buffer, Nat.equal); + +run(suite("groupBy", +[ + test( + "num groups", + groups.size(), + M.equals(T.nat(5)) + ), + test( + "group 0 capacity", + groups.get(0).capacity(), + M.equals(T.nat(9)) + ), + test( + "group 0 elements", + B.toArray(groups.get(0)), + M.equals(T.array(T.natTestable, [2, 2, 2])) + ), + test( + "group 1 capacity", + groups.get(1).capacity(), + M.equals(T.nat(6)) + ), + test( + "group 1 elements", + B.toArray(groups.get(1)), + M.equals(T.array(T.natTestable, [1])) + ), + test( + "group 4 capacity", + groups.get(4).capacity(), + M.equals(T.nat(2)) + ), + test( + "group 4 elements", + B.toArray(groups.get(4)), + M.equals(T.array(T.natTestable, [1, 1])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +groups := B.groupBy(buffer, Nat.equal); + +run(suite("groupBy clear", +[ + test( + "num groups", + groups.size(), + M.equals(T.nat(0)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(0) +}; + +groups := B.groupBy(buffer, Nat.equal); + +run(suite("groupBy clear", +[ + test( + "num groups", + groups.size(), + M.equals(T.nat(1)) + ), + test( + "group 0 elements", + B.toArray(groups.get(0)), + M.equals(T.array(T.natTestable, [0, 0, 0, 0, 0])) + ) +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +buffer := B.prefix(buffer, 3); + +run(suite("prefix", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(5)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2])) + ) +])); + +/* --------------------------------------- */ +buffer.clear(); + +buffer := B.prefix(buffer, 0); + +run(suite("prefix of empty", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(1)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [] : [Nat])) + ) +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +buffer := B.prefix(buffer, 5); + +run(suite("trivial prefix", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(8)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4])) + ) +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +buffer2.clear(); + +for (i in Iter.range(0, 2)) { + buffer2.add(i); +}; + +buffer3.clear(); + +buffer3.add(2); +buffer3.add(1); +buffer3.add(0); + +run(suite("isPrefixOf", +[ + test( + "normal prefix", + B.isPrefixOf(buffer2, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "identical buffers", + B.isPrefixOf(buffer, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "one empty buffer", + B.isPrefixOf(B.Buffer(3), buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "not prefix", + B.isPrefixOf(buffer3, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not prefix from length", + B.isPrefixOf(buffer, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not prefix of empty", + B.isPrefixOf(buffer, B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), + test( + "empty prefix of empty", + B.isPrefixOf(B.Buffer(4), B.Buffer(3), Nat.equal), + M.equals(T.bool(true)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) }; -for (i in I.range(0, 123)) { - assert (a.get(i) == i); + +buffer2.clear(); + +for (i in Iter.range(0, 2)) { + buffer2.add(i); }; +buffer3.clear(); + +buffer3.add(2); +buffer3.add(1); +buffer3.add(0); + +run(suite("isStrictPrefixOf", +[ + test( + "normal prefix", + B.isStrictPrefixOf(buffer2, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "identical buffers", + B.isStrictPrefixOf(buffer, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "one empty buffer", + B.isStrictPrefixOf(B.Buffer(3), buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "not prefix", + B.isStrictPrefixOf(buffer3, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not prefix from length", + B.isStrictPrefixOf(buffer, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not prefix of empty", + B.isStrictPrefixOf(buffer, B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), + test( + "empty prefix of empty", + B.isStrictPrefixOf(B.Buffer(4), B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); -// test repeated appending -let b = B.Buffer(3); -for (i in I.range(0, 123)) { - b.append(a); +for (i in Iter.range(0, 4)) { + buffer.add(i) }; -Prim.debugPrint(debug_show(a.toArray())); -Prim.debugPrint(debug_show(b.toArray())); +buffer := B.subBuffer(buffer, 1, 3); + +run(suite("subBuffer", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(5)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [1, 2, 3])) + ) +])); + +/* --------------------------------------- */ +buffer.clear(); -// test repeated removing -for (i in I.revRange(123, 0)) { - assert(O.unwrap(a.removeLast()) == i); +for (i in Iter.range(0, 4)) { + buffer.add(i) }; -O.assertNull(a.removeLast()); -func natArrayIter(elems:[Nat]) : I.Iter = object { - var pos = 0; - let count = elems.size(); - public func next() : ?Nat { - if (pos == count) { null } else { - let elem = ?elems[pos]; - pos += 1; - elem - } - } +run(suite("subBuffer edge cases", +[ + test( + "prefix", + B.prefix(buffer, 3), + M.equals({ {item = B.subBuffer(buffer, 0, 3)} and NatBufferTestable}) + ), + test( + "suffix", + B.suffix(buffer, 3), + M.equals({ {item = B.subBuffer(buffer, 2, 3)} and NatBufferTestable}) + ), + test( + "empty", + B.toArray(B.subBuffer(buffer, 2, 0)), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "trivial", + B.subBuffer(buffer, 0, buffer.size()), + M.equals({ {item = buffer} and NatBufferTestable}) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) }; -func natVarArrayIter(elems:[var Nat]) : I.Iter = object { - var pos = 0; - let count = elems.size(); - public func next() : ?Nat { - if (pos == count) { null } else { - let elem = ?elems[pos]; - pos += 1; - elem - } - } -}; - -func natIterEq(a:I.Iter, b:I.Iter) : Bool { - switch (a.next(), b.next()) { - case (null, null) { true }; - case (?x, ?y) { - if (x == y) { natIterEq(a, b) } - else { false } - }; - case (_, _) { false }; - } -}; - -// regression test: buffers with extra space are converted to arrays of the correct length -do { - let bigLen = 100; - let len = 3; - let c = B.Buffer(bigLen); - assert (len < bigLen); - for (i in I.range(0, len - 1)) { - Prim.debugPrint(debug_show(i)); - c.add(i); - }; - assert (c.size() == len); - assert (c.toArray().size() == len); - assert (natIterEq(c.vals(), natArrayIter(c.clone().toArray()))); - assert (c.toVarArray().size() == len); - assert (natIterEq(c.vals(), natVarArrayIter(c.clone().toVarArray()))); -}; - -// regression test: initially-empty buffers grow, element-by-element -do { - let c = B.Buffer(0); - assert (c.toArray().size() == 0); - assert (c.toVarArray().size() == 0); - c.add(0); - assert (c.toArray().size() == 1); - assert (c.toVarArray().size() == 1); - c.add(0); - assert (c.toArray().size() == 2); - assert (c.toVarArray().size() == 2); +buffer2.clear(); + +for (i in Iter.range(0, 2)) { + buffer2.add(i); }; -let {run;test;suite} = Suite; -run(suite("array", +buffer3.clear(); + +for (i in Iter.range(1, 3)) { + buffer3.add(i); +}; + +run(suite("isSubBufferOf", [ test( - "fromArray", - B.fromArray([0, 1, 2, 3]).toArray(), - M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + "normal subBuffer", + B.isSubBufferOf(buffer3, buffer, Nat.equal), + M.equals(T.bool(true)) ), test( - "fromVarArray", - B.fromVarArray([var 0, 1, 2, 3]).toArray(), - M.equals(T.array(T.natTestable, [0, 1, 2, 3])) + "prefix", + B.isSubBufferOf(buffer2, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "identical buffers", + B.isSubBufferOf(buffer, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "one empty buffer", + B.isSubBufferOf(B.Buffer(3), buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "not subBuffer", + B.isSubBufferOf(buffer3, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not subBuffer from length", + B.isSubBufferOf(buffer, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not subBuffer of empty", + B.isSubBufferOf(buffer, B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), + test( + "empty subBuffer of empty", + B.isSubBufferOf(B.Buffer(4), B.Buffer(3), Nat.equal), + M.equals(T.bool(true)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +buffer2.clear(); + +for (i in Iter.range(0, 2)) { + buffer2.add(i); +}; + +buffer3.clear(); + +for (i in Iter.range(1, 3)) { + buffer3.add(i); +}; + +buffer4 := B.Buffer(4); + +for (i in Iter.range(3, 4)) { + buffer4.add(i); +}; + +run(suite("isStrictSubBufferOf", +[ + test( + "normal strict subBuffer", + B.isStrictSubBufferOf(buffer3, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "prefix", + B.isStrictSubBufferOf(buffer2, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "suffix", + B.isStrictSubBufferOf(buffer4, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "identical buffers", + B.isStrictSubBufferOf(buffer, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "one empty buffer", + B.isStrictSubBufferOf(B.Buffer(3), buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "not subBuffer", + B.isStrictSubBufferOf(buffer3, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not subBuffer from length", + B.isStrictSubBufferOf(buffer, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not subBuffer of empty", + B.isStrictSubBufferOf(buffer, B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), + test( + "empty not strict subBuffer of empty", + B.isStrictSubBufferOf(B.Buffer(4), B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +buffer := B.suffix(buffer, 3); + +run(suite("suffix", +[ + test( + "capacity", + buffer.capacity(), + M.equals(T.nat(5)) + ), + test( + "elements", + B.toArray(buffer), + M.equals(T.array(T.natTestable, [2, 3, 4])) ) ])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +run(suite("suffix edge cases", +[ + test( + "empty", + B.toArray(B.prefix(buffer, 0)), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "trivial", + B.prefix(buffer, buffer.size()), + M.equals({ {item = buffer} and NatBufferTestable}) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +buffer2.clear(); +for (i in Iter.range(3, 4)) { + buffer2.add(i) +}; + +buffer3.clear(); + +buffer3.add(2); +buffer3.add(1); +buffer3.add(0); + +run(suite("isSuffixOf", +[ + test( + "normal suffix", + B.isSuffixOf(buffer2, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "identical buffers", + B.isSuffixOf(buffer, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "one empty buffer", + B.isSuffixOf(B.Buffer(3), buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "not suffix", + B.isSuffixOf(buffer3, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not suffix from length", + B.isSuffixOf(buffer, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not suffix of empty", + B.isSuffixOf(buffer, B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), + test( + "empty suffix of empty", + B.isSuffixOf(B.Buffer(4), B.Buffer(3), Nat.equal), + M.equals(T.bool(true)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +buffer2.clear(); +for (i in Iter.range(3, 4)) { + buffer2.add(i) +}; + +buffer3.clear(); + +buffer3.add(2); +buffer3.add(1); +buffer3.add(0); + +run(suite("isStrictSuffixOf", +[ + test( + "normal suffix", + B.isStrictSuffixOf(buffer2, buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "identical buffers", + B.isStrictSuffixOf(buffer, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "one empty buffer", + B.isStrictSuffixOf(B.Buffer(3), buffer, Nat.equal), + M.equals(T.bool(true)) + ), + test( + "not suffix", + B.isStrictSuffixOf(buffer3, buffer, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not suffix from length", + B.isStrictSuffixOf(buffer, buffer2, Nat.equal), + M.equals(T.bool(false)) + ), + test( + "not suffix of empty", + B.isStrictSuffixOf(buffer, B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), + test( + "empty suffix of empty", + B.isStrictSuffixOf(B.Buffer(4), B.Buffer(3), Nat.equal), + M.equals(T.bool(false)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +run(suite("takeWhile", +[ + test( + "normal case", + B.toArray(B.takeWhile(buffer, func x = x < 3)), + M.equals(T.array(T.natTestable, [0, 1, 2])) + ), + test( + "empty", + B.toArray(B.takeWhile(B.Buffer(3), func x = x < 3)), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 4)) { + buffer.add(i) +}; + +run(suite("dropWhile", +[ + test( + "normal case", + B.toArray(B.dropWhile(buffer, func x = x < 3)), + M.equals(T.array(T.natTestable, [3, 4])) + ), + test( + "empty", + B.toArray(B.dropWhile(B.Buffer(3), func x = x < 3)), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "drop all", + B.toArray(B.dropWhile(buffer, func _ = true)), + M.equals(T.array(T.natTestable, [] : [Nat])) + ), + test( + "drop none", + B.toArray(B.dropWhile(buffer, func _ = false)), + M.equals(T.array(T.natTestable, [0, 1, 2, 3, 4])) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(1, 6)) { + buffer.add(i) +}; + +run(suite("binarySearch", +[ + test( + "find in middle", + B.binarySearch(2, buffer, Nat.compare), + M.equals(T.optional(T.natTestable, ?1)) + ), + test( + "find first", + B.binarySearch(1, buffer, Nat.compare), + M.equals(T.optional(T.natTestable, ?0)) + ), + test( + "find last", + B.binarySearch(6, buffer, Nat.compare), + M.equals(T.optional(T.natTestable, ?5)) + ), + test( + "not found to the right", + B.binarySearch(10, buffer, Nat.compare), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "not found to the left", + B.binarySearch(0, buffer, Nat.compare), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +for (i in Iter.range(0, 6)) { + buffer.add(i) +}; + +run(suite("indexOf", +[ + test( + "find in middle", + B.indexOf(2, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?2)) + ), + test( + "find first", + B.indexOf(0, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?0)) + ), + test( + "find last", + B.indexOf(6, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?6)) + ), + test( + "not found", + B.indexOf(10, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "empty", + B.indexOf(100, B.Buffer(3), Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +buffer.add(2); // 0 +buffer.add(2); // 1 +buffer.add(1); // 2 +buffer.add(10);// 3 +buffer.add(1); // 4 +buffer.add(0); // 5 +buffer.add(10);// 6 +buffer.add(3); // 7 +buffer.add(0); // 8 + +run(suite("lastIndexOf", +[ + test( + "find in middle", + B.lastIndexOf(10, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?6)) + ), + test( + "find only", + B.lastIndexOf(3, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?7)) + ), + test( + "find last", + B.lastIndexOf(0, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?8)) + ), + test( + "not found", + B.lastIndexOf(100, buffer, Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "empty", + B.lastIndexOf(100, B.Buffer(3), Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), +])); + +/* --------------------------------------- */ +buffer.clear(); + +buffer.add(2); // 0 +buffer.add(2); // 1 +buffer.add(1); // 2 +buffer.add(10);// 3 +buffer.add(1); // 4 +buffer.add(10);// 5 +buffer.add(3); // 6 +buffer.add(0); // 7 + +run(suite("indexOfBuffer", +[ + test( + "find in middle", + B.indexOfBuffer(B.fromArray([1, 10, 1]), buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?2)) + ), + test( + "find first", + B.indexOfBuffer(B.fromArray([2, 2, 1, 10]), buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?0)) + ), + test( + "find last", + B.indexOfBuffer(B.fromArray([0]), buffer, Nat.equal), + M.equals(T.optional(T.natTestable, ?7)) + ), + test( + "not found", + B.indexOfBuffer(B.fromArray([99, 100, 1]), buffer, Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "search for empty buffer", + B.indexOfBuffer(B.fromArray([]), buffer, Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "search through empty buffer", + B.indexOfBuffer(B.fromArray([1, 2, 3]), B.Buffer(2), Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "search for empty in empty", + B.indexOfBuffer(B.Buffer(2), B.Buffer(3), Nat.equal), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), +]));