Skip to content

Commit

Permalink
Merge master into next-moc
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Jun 27, 2023
2 parents 5f700d0 + 0400a68 commit 430ab58
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 14 deletions.
124 changes: 118 additions & 6 deletions src/Array.mo
Original file line number Diff line number Diff line change
Expand Up @@ -723,12 +723,124 @@ module {
///
/// let array = [1,2,3,4,5];
/// let subArray = Array.subArray<Nat>(array, 2, 3);
/// ```
/// Runtime: O(length);
/// Space: O(length);
public func subArray<X>(arr: [X], start: Nat, length: Nat): [X] {
if (start + length > arr.size()) { Prim.trap("Array.subArray") };
tabulate<X>(length, func(i) {
arr[start + i]
});
public func subArray<X>(array : [X], start : Nat, length : Nat) : [X] {
if (start + length > array.size()) { Prim.trap("Array.subArray") };
tabulate<X>(
length,
func(i) {
array[start + i]
}
)
};
};

/// Returns the index of the first `element` in the `array`.
///
/// ```motoko include=import
/// import Char "mo:base/Char";
/// let array = ['c', 'o', 'f', 'f', 'e', 'e'];
/// assert Array.indexOf<Char>('c', array, Char.equal) == ?0;
/// assert Array.indexOf<Char>('f', array, Char.equal) == ?2;
/// assert Array.indexOf<Char>('g', array, Char.equal) == null;
/// ```
///
/// Runtime: O(array.size());
/// Space: O(1);
public func indexOf<X>(element : X, array : [X], equal : (X, X) -> Bool) : ?Nat = nextIndexOf<X>(element, array, 0, equal);

/// Returns the index of the next occurence of `element` in the `array` starting from the `from` index (inclusive).
///
/// ```motoko include=import
/// import Char "mo:base/Char";
/// let array = ['c', 'o', 'f', 'f', 'e', 'e'];
/// assert Array.nextIndexOf<Char>('c', array, 0, Char.equal) == ?0;
/// assert Array.nextIndexOf<Char>('f', array, 0, Char.equal) == ?2;
/// assert Array.nextIndexOf<Char>('f', array, 2, Char.equal) == ?2;
/// assert Array.nextIndexOf<Char>('f', array, 3, Char.equal) == ?3;
/// assert Array.nextIndexOf<Char>('f', array, 4, Char.equal) == null;
/// ```
///
/// Runtime: O(array.size());
/// Space: O(1);
public func nextIndexOf<X>(element : X, array : [X], fromInclusive : Nat, equal : (X, X) -> Bool) : ?Nat {
var i = fromInclusive;
let n = array.size();
while (i < n) {
if (equal(array[i], element)) {
return ?i
} else {
i += 1
}
};
null
};

/// Returns the index of the last `element` in the `array`.
///
/// ```motoko include=import
/// import Char "mo:base/Char";
/// let array = ['c', 'o', 'f', 'f', 'e', 'e'];
/// assert Array.lastIndexOf<Char>('c', array, Char.equal) == ?0;
/// assert Array.lastIndexOf<Char>('f', array, Char.equal) == ?3;
/// assert Array.lastIndexOf<Char>('e', array, Char.equal) == ?5;
/// assert Array.lastIndexOf<Char>('g', array, Char.equal) == null;
/// ```
///
/// Runtime: O(array.size());
/// Space: O(1);
public func lastIndexOf<X>(element : X, array : [X], equal : (X, X) -> Bool) : ?Nat = prevIndexOf<X>(element, array, array.size(), equal);

/// Returns the index of the previous occurance of `element` in the `array` starting from the `from` index (exclusive).
///
/// ```motoko include=import
/// import Char "mo:base/Char";
/// let array = ['c', 'o', 'f', 'f', 'e', 'e'];
/// assert Array.prevIndexOf<Char>('c', array, array.size(), Char.equal) == ?0;
/// assert Array.prevIndexOf<Char>('e', array, array.size(), Char.equal) == ?5;
/// assert Array.prevIndexOf<Char>('e', array, 5, Char.equal) == ?4;
/// assert Array.prevIndexOf<Char>('e', array, 4, Char.equal) == null;
/// ```
///
/// Runtime: O(array.size());
/// Space: O(1);
public func prevIndexOf<T>(element : T, array : [T], fromExclusive : Nat, equal : (T, T) -> Bool) : ?Nat {
var i = fromExclusive;
while (i > 0) {
i -= 1;
if (equal(array[i], element)) {
return ?i
}
};
null
};

/// Returns an iterator over a slice of the given array.
///
/// ```motoko include=import
/// let array = [1, 2, 3, 4, 5];
/// let s = Array.slice<Nat>(array, 3, array.size());
/// assert s.next() == ?4;
/// assert s.next() == ?5;
/// assert s.next() == null;
///
/// let s = Array.slice<Nat>(array, 0, 0);
/// assert s.next() == null;
/// ```
///
/// Runtime: O(1)
/// Space: O(1)
public func slice<X>(array : [X], fromInclusive : Nat, toExclusive : Nat) : I.Iter<X> = object {
var i = fromInclusive;

public func next() : ?X {
if (i >= toExclusive) {
return null
};
let result = array[i];
i += 1;
return ?result
}
}
}
49 changes: 49 additions & 0 deletions src/Text.mo
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,55 @@ module {
/// ```
public func toIter(t : Text) : Iter.Iter<Char> = t.chars();

/// Creates a new `Array` containing characters of the given `Text`.
///
/// Equivalent to `Iter.toArray(t.chars())`.
///
/// ```motoko include=import
/// assert Text.toArray("Café") == ['C', 'a', 'f', 'é'];
/// ```
///
/// Runtime: O(t.size())
/// Space: O(t.size())
public func toArray(t : Text) : [Char] {
let cs = t.chars();
// We rely on Array_tabulate's implementation details: it fills
// the array from left to right sequentially.
Prim.Array_tabulate<Char>(
t.size(),
func _ {
switch (cs.next()) {
case (?c) { c };
case (null) { Prim.trap("Text.toArray") };
};
}
)
};

/// Creates a new mutable `Array` containing characters of the given `Text`.
///
/// Equivalent to `Iter.toArrayMut(t.chars())`.
///
/// ```motoko include=import
/// assert Text.toVarArray("Café") == [var 'C', 'a', 'f', 'é'];
/// ```
///
/// Runtime: O(t.size())
/// Space: O(t.size())
public func toVarArray(t : Text) : [var Char] {
let n = t.size();
if (n == 0) {
return [var];
};
let array = Prim.Array_init<Char>(n, ' ');
var i = 0;
for (c in t.chars()) {
array[i] := c;
i += 1;
};
array
};

/// Creates a `Text` value from a `Char` iterator.
///
/// ```motoko include=import
Expand Down
84 changes: 76 additions & 8 deletions test/Array.test.mo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Array "mo:base/Array";
import Int "mo:base/Int";
import Char "mo:base/Char";
import Nat "mo:base/Nat";
import Text "mo:base/Text";
import Result "mo:base/Result";
Expand Down Expand Up @@ -384,24 +385,91 @@ let suite = Suite.suite(
),
Suite.test(
"subarray if including entire array",
Array.subArray<Nat>([2,4,6,8,10], 0, 5),
M.equals(T.array(T.natTestable, [2,4,6,8,10]))
Array.subArray<Nat>([2, 4, 6, 8, 10], 0, 5),
M.equals(T.array(T.natTestable, [2, 4, 6, 8, 10]))
),
Suite.test(
"subarray if including middle of array",
Array.subArray<Nat>([2,4,6,8,10], 1, 3),
M.equals(T.array(T.natTestable, [4,6,8]))
Array.subArray<Nat>([2, 4, 6, 8, 10], 1, 3),
M.equals(T.array(T.natTestable, [4, 6, 8]))
),
Suite.test(
"subarray if including start, but not end of array",
Array.subArray<Nat>([2,4,6,8,10], 0, 3),
M.equals(T.array(T.natTestable, [2,4,6]))
Array.subArray<Nat>([2, 4, 6, 8, 10], 0, 3),
M.equals(T.array(T.natTestable, [2, 4, 6]))
),
Suite.test(
"subarray if including end, but not start of array",
Array.subArray<Nat>([2,4,6,8,10], 2, 3),
M.equals(T.array(T.natTestable, [6,8,10]))
Array.subArray<Nat>([2, 4, 6, 8, 10], 2, 3),
M.equals(T.array(T.natTestable, [6, 8, 10]))
),

Suite.test(
"nextIndexOf start",
Array.nextIndexOf<Char>('c', ['c', 'o', 'f', 'f', 'e', 'e'], 0, Char.equal),
M.equals(T.optional(T.natTestable, ?0))
),
Suite.test(
"nextIndexOf not found from offset",
Array.nextIndexOf<Char>('c', ['c', 'o', 'f', 'f', 'e', 'e'], 1, Char.equal),
M.equals(T.optional(T.natTestable, null : ?Nat))
),
Suite.test(
"nextIndexOf middle",
Array.nextIndexOf<Char>('f', ['c', 'o', 'f', 'f', 'e', 'e'], 0, Char.equal),
M.equals(T.optional(T.natTestable, ?2))
),
Suite.test(
"nextIndexOf repeat",
Array.nextIndexOf<Char>('f', ['c', 'o', 'f', 'f', 'e', 'e'], 2, Char.equal),
M.equals(T.optional(T.natTestable, ?2))
),
Suite.test(
"nextIndexOf start from the middle",
Array.nextIndexOf<Char>('f', ['c', 'o', 'f', 'f', 'e', 'e'], 3, Char.equal),
M.equals(T.optional(T.natTestable, ?3))
),
Suite.test(
"nextIndexOf not found",
Array.nextIndexOf<Char>('g', ['c', 'o', 'f', 'f', 'e', 'e'], 0, Char.equal),
M.equals(T.optional(T.natTestable, null : ?Nat))
),
Suite.test(
"nextIndexOf index out of bounds",
Array.nextIndexOf<Char>('f', ['c', 'o', 'f', 'f', 'e', 'e'], 100, Char.equal),
M.equals(T.optional(T.natTestable, null : ?Nat))
),

Suite.test(
"prevIndexOf first",
Array.prevIndexOf<Char>('c', ['c', 'o', 'f', 'f', 'e', 'e'], 6, Char.equal),
M.equals(T.optional(T.natTestable, ?0))
),
Suite.test(
"prevIndexOf last",
Array.prevIndexOf<Char>('e', ['c', 'o', 'f', 'f', 'e', 'e'], 6, Char.equal),
M.equals(T.optional(T.natTestable, ?5))
),
Suite.test(
"prevIndexOf middle",
Array.prevIndexOf<Char>('f', ['c', 'o', 'f', 'f', 'e', 'e'], 6, Char.equal),
M.equals(T.optional(T.natTestable, ?3))
),
Suite.test(
"prevIndexOf start from the middle",
Array.prevIndexOf<Char>('f', ['c', 'o', 'f', 'f', 'e', 'e'], 3, Char.equal),
M.equals(T.optional(T.natTestable, ?2))
),
Suite.test(
"prevIndexOf existing not found",
Array.prevIndexOf<Char>('f', ['c', 'o', 'f', 'f', 'e', 'e'], 2, Char.equal),
M.equals(T.optional(T.natTestable, null : ?Nat))
),
Suite.test(
"prevIndexOf not found",
Array.prevIndexOf<Char>('g', ['c', 'o', 'f', 'f', 'e', 'e'], 6, Char.equal),
M.equals(T.optional(T.natTestable, null : ?Nat))
)
]
);

Expand Down
32 changes: 32 additions & 0 deletions test/Iter.test.mo
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,36 @@ do {
let expected : [Nat] = [1, 2, 3, 4, 5];
let actual = Iter.toArray(Iter.sort<Nat>(input.vals(), Nat.compare));
assert Array.equal<Nat>(expected, actual, func(x1, x2) { x1 == x2 })
};

do {
Debug.print(" Array slice");

let input : [Nat] = [4, 3, 1, 2, 5];

let sEmpty = Array.slice(input, 0, 0);
assert sEmpty.next() == null;

let sPrefix = Array.slice(input, 0, 1);
assert sPrefix.next() == ?4;
assert sPrefix.next() == null;

let sSuffix = Array.slice(input, 4, 5);
assert sSuffix.next() == ?5;
assert sSuffix.next() == null;

let sInfix = Array.slice(input, 3, 4);
assert sInfix.next() == ?2;
assert sInfix.next() == null;

let sFull = Array.slice(input, 0, input.size());
assert sFull.next() == ?4;
assert sFull.next() == ?3;
assert sFull.next() == ?1;
assert sFull.next() == ?2;
assert sFull.next() == ?5;
assert sFull.next() == null;

let sEmptier = Array.slice(input, input.size(), input.size());
assert sEmptier.next() == null
}
18 changes: 18 additions & 0 deletions test/Text.test.mo
Original file line number Diff line number Diff line change
Expand Up @@ -1030,4 +1030,22 @@ run(
)
]
)
);

run(
suite(
"array-conversions",
[
test(
"toArray-example",
Text.toArray("Café"),
M.equals(T.array<Char>(T.charTestable, ['C', 'a', 'f', 'é']))
),
test(
"toArray-example",
Array.freeze(Text.toVarArray("Café")),
M.equals(T.array<Char>(T.charTestable, ['C', 'a', 'f', 'é']))
)
]
)
)

0 comments on commit 430ab58

Please sign in to comment.