From d52aecd7dabe3f5f8169b7680fe0a0702a67b0e3 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 21 Oct 2022 13:43:49 +0200 Subject: [PATCH] Buffer Class Extension (#417) Base Library Extension Doc: https://docs.google.com/document/d/1uKouujLL3KueLpqjjFV2gd4dqu_6GYD107WBb6_GSVE/edit - [x] Complete unit tests - [x] Consider making initCapacity an optional value with a default initial capacity - [x] While loop iterations over Arrays may be faster with an iterator due to a compiler optimization - [x] In functions where a new buffer is being constructed, have some consistent scheme for how to determine the new buffer's capacity. Should the new buffer have just enough space to hold its element, or be given some insertion leeway? - [x] Some class methods can be moved outside of the class since they don't need access to the underlying array - [x] Consider behavior of transpose on empty Buffers - [x] sort() can be optimized by hoisting helper functions - [x] Restore broken methods in Array.mo and Hash.mo - [x] Compare with original Buffer class and try to keep some level of backwards compatibility / deprecate old functions - [x] Fix naming to be consistent with user friendliness, other classes, and style / interface guide - [x] Add user friendly error messages / error checking - [x] Cleanup tests - [x] Add documentation for each function - [ ] Add examples for each function (put up as a separate PR) - [ ] A primitive array tabulate for var arrays would allow some nice optimizations (put up as a separate PR) - [x] Profile this class to see if a more scalable dynamic sequence is necessary and to resolve ambiguous design decisions This last point is related to the RRB Trees, Rope, and Finger Trees data structures that I was discussing before. For various reasons, I am leaning towards an RRB Tree implementation of a scalable sequence structure, if it is necessary. See a talk on this data structure here: https://www.youtube.com/watch?v=sPhpelUfu8Q Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Kento Sugama --- .gitignore | 7 + src/Array.mo | 88 +- src/Buffer.mo | 1820 +++++++++++++++++++++++-- src/Iter.mo | 2 +- test/arrayTest.mo | 16 + test/bufTest.mo | 3297 +++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 5006 insertions(+), 224 deletions(-) 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)) + ), +]));