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