// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Creates a new dynamic array from a fixed-size array.
///
/// Parameters:
///
/// * `arr` : The fixed-size array to convert. The elements of this array will be
/// copied to the new array.
///
/// Returns a new dynamic array containing all elements from the input fixed-size
/// array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let fixed = FixedArray::make(3, 42)
/// let dynamic = Array::from_fixed_array(fixed)
/// debug_inspect(dynamic, content="[42, 42, 42]")
/// }
/// ```
#owned(arr)
pub fn[T] Array::from_fixed_array(arr : FixedArray[T]) -> Array[T] {
let len = arr.length()
Array::unsafe_make_and_blit_from_fixed(arr, allocate_len=len, len~)
}
///|
/// Creates a new array with a specified length and initializes all elements with
/// the given value.
///
/// Parameters:
///
/// * `length` : The length of the array to create. Must be a non-negative
/// integer.
/// * `initial_value` : The value used to initialize all elements in the array.
///
/// Returns a new array of type `Array[T]` with `length` elements, where each
/// element is initialized to `initial_value`.
///
/// Throws an error if `length` is negative.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = Array::make(3, 42)
/// debug_inspect(arr, content="[42, 42, 42]")
/// }
/// ```
///
/// WARNING: A common pitfall is creating with the same initial value, for example:
/// ```mbt check
/// test {
/// let two_dimension_array = Array::make(10, Array::make(10, 0))
/// two_dimension_array[0][5] = 10
/// @test.assert_eq(two_dimension_array[5][5], 10)
/// }
/// ```
/// This is because all the cells reference to the same object (the Array[Int] in this case).
/// One should use makei() instead which creates an object for each index.
#owned(elem)
pub fn[T] Array::make(len : Int, elem : T) -> Array[T] {
let arr = Array::make_uninit(len)
for i in 0.. i * 2)
/// debug_inspect(arr, content="[0, 2, 4]")
/// }
/// ```
#locals(f)
pub fn[T] Array::makei(length : Int, f : (Int) -> T raise?) -> Array[T] raise? {
if length <= 0 {
[]
} else {
let array = Array::make_uninit(length)
for i in 0.. Int {
self.buffer().0.length()
}
///|
/// Retrieves the element at the specified index from an array without bounds
/// checking.
///
/// Parameters:
///
/// * `array` : The array from which to retrieve the element.
/// * `index` : The position in the array from which to retrieve the element.
///
/// Returns the element at the specified index.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr : Array[Int] = [1, 2, 3]
/// inspect(arr.unsafe_get(1), content="2")
/// }
/// ```
///
#intrinsic("%array.unsafe_get")
pub fn[T] Array::unsafe_get(self : Array[T], idx : Int) -> T {
self.buffer().unsafe_get(idx)
}
///|
/// Retrieves an element from the array at the specified index.
///
/// Parameters:
///
/// * `array` : The array to get the element from.
/// * `index` : The position in the array from which to retrieve the element.
///
/// Returns the element at the specified index.
///
/// Throws a panic if the index is negative or greater than or equal to the
/// length of the array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr : ReadOnlyArray[Int] = [1, 2, 3]
/// inspect(arr[1], content="2")
/// }
/// ```
///
#intrinsic("%array.get")
#alias("_[_]")
pub fn[T] Array::at(self : Array[T], index : Int) -> T {
let len = self.length()
guard! index >= 0 && index < len
self.buffer().unsafe_get(index)
}
///|
/// Retrieves the element at the specified index from the array.
///
/// Parameters:
///
/// * `self` : The array to get the element from.
/// * `index` : The position in the array from which to retrieve the element.
///
/// Returns `Some(element)` if the index is within bounds, or `None` if the index
/// is out of bounds.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// debug_inspect(arr.get(-1), content="None")
/// debug_inspect(arr.get(0), content="Some(1)")
/// debug_inspect(arr.get(3), content="None")
/// }
/// ```
pub fn[T] Array::get(self : Array[T], index : Int) -> T? {
let len = self.length()
guard index >= 0 && index < len else { None }
Some(self.unsafe_get(index))
}
///|
/// Write `val` to `idx` without bounds checking.
///
/// This is unsafe: caller must ensure `0 <= idx < self.length()`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// arr.unsafe_set(1, 99)
/// debug_inspect(arr, content="[1, 99, 3]")
/// }
/// ```
#intrinsic("%array.unsafe_set")
#owned(val)
pub fn[T] Array::unsafe_set(self : Array[T], idx : Int, val : T) -> Unit {
self.buffer().unsafe_set(idx, val)
}
///|
/// Sets the element at the specified index in the array to a new value. The
/// original value at that index is overwritten.
///
/// Parameters:
///
/// * `array` : The array to modify.
/// * `index` : The position in the array where the value will be set.
/// * `value` : The new value to assign at the specified index.
///
/// Throws an error if `index` is negative or greater than or equal to the length
/// of the array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// arr[1] = 42
/// debug_inspect(arr, content="[1, 42, 3]")
/// }
/// ```
///
#intrinsic("%array.set")
#alias("_[_]=_")
#owned(value)
pub fn[T] Array::set(self : Array[T], index : Int, value : T) -> Unit {
let len = self.length()
guard! index >= 0 && index < len
self.buffer().unsafe_set(index, value)
}
///|
/// Compares two arrays for equality. Returns true if both arrays have the same
/// length and contain equal elements in the same order.
///
/// Parameters:
///
/// * `self` : The first array to compare.
/// * `other` : The second array to compare.
///
/// Returns true if the arrays are equal, false otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr1 = [1, 2, 3]
/// let arr2 = [1, 2, 3]
/// let arr3 = [1, 2, 4]
/// inspect(arr1 == arr2, content="true")
/// inspect(arr1 == arr3, content="false")
/// }
/// ```
pub impl[T : Eq] Eq for Array[T] with fn equal(self, other) {
let self_len = self.length()
let other_len = other.length()
guard self_len == other_len else { return false }
for i in 0.. arr1
/// inspect(arr1.compare(arr3), content="1") // arr1 > arr3 (longer)
/// inspect(arr1.compare(arr1), content="0") // arr1 = arr1
/// }
/// ```
pub impl[T : Compare] Compare for Array[T] with fn compare(self, other) {
let len_self = self.length()
let len_other = other.length()
let cmp = len_self.compare(len_other)
guard cmp is 0 else { return cmp }
for i in 0.. sum += x)
/// inspect(sum, content="6")
/// }
/// ```
/// This method uses the array iterator. Structural mutations during traversal
/// are unsupported: appended elements are not visited, and shrinking the array
/// with operations such as `remove`, `truncate`, `clear`, or `drain` may cause
/// later iterator steps to fail.
#locals(f)
pub fn[T] Array::each(self : Array[T], f : (T) -> Unit raise?) -> Unit raise? {
for v in self {
f(v)
}
}
///|
/// Iterates over the elements of the array in reverse order, applying the given
/// function to each element.
///
/// Parameters:
///
/// * `array` : The array to iterate over.
/// * `f` : A function that takes an element of type `T` and returns `Unit`. This
/// function is applied to each element of the array in reverse order.
///
/// Example:
///
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// let mut sum = 0
/// v.rev_each(x => sum = sum - x)
/// @json.json_inspect(sum, content=-12)
/// }
/// ```
#locals(f)
pub fn[T] Array::rev_each(
self : Array[T],
f : (T) -> Unit raise?,
) -> Unit raise? {
let len = self.length()
for i in 0.. sum += x + i)
/// @test.assert_eq(sum, 15)
/// }
/// ```
#locals(f)
pub fn[T] Array::rev_eachi(
self : Array[T],
f : (Int, T) -> Unit raise?,
) -> Unit raise? {
let len = self.length()
for i in 0.. sum += x + i)
/// inspect(sum, content="15")
/// }
/// ```
#locals(f)
pub fn[T] Array::eachi(
self : Array[T],
f : (Int, T) -> Unit raise?,
) -> Unit raise? {
for i, v in self {
f(i, v)
}
}
///|
/// Checks whether all elements satisfy the predicate.
///
/// # Example
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// assert_true(arr.all(x => x < 6))
/// assert_false(arr.all(x => x < 5))
/// }
/// ```
#locals(f)
#alias(every)
pub fn[T] Array::all(self : Array[T], f : (T) -> Bool raise?) -> Bool raise? {
// Inlined for #locals(f); see Array::any.
for v in self {
if !f(v) {
return false
}
}
true
}
///|
/// Checks whether any element satisfies the predicate.
///
/// # Example
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// assert_true(arr.any(x => x < 6))
/// assert_false(arr.any(x => x < 1))
/// }
/// ```
#locals(f)
#alias(exists)
pub fn[T] Array::any(self : Array[T], f : (T) -> Bool raise?) -> Bool raise? {
// Inlined rather than delegating to `self[:].any(f)`: passing `f` onward
// counts as an escape, which would reject `#locals(f)` (see issue about
// propagating localness through #locals-annotated callees).
for v in self {
if f(v) {
return true
}
}
false
}
///|
/// Clears the array, removing all values.
///
/// This method has no effect on the allocated capacity of the array, only setting the length to 0.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// v.clear()
/// @test.assert_eq(v.length(), 0)
/// }
/// ```
pub fn[T] Array::clear(self : Array[T]) -> Unit {
self.unsafe_truncate_to_length(0)
}
///|
/// Maps a function over the elements of the array.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// let v2 = v.map(x => x + 1)
/// @test.assert_eq(v2, [4, 5, 6])
/// }
/// ```
#locals(f)
pub fn[T, U] Array::map(
self : Array[T],
f : (T) -> U raise?,
) -> Array[U] raise? {
let arr = Array::make_uninit(self.length())
for i, v in self {
arr.unsafe_set(i, f(v))
}
arr
}
///|
/// Maps a function over the elements of the array in place.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// v.map_in_place(x => x + 1)
/// @test.assert_eq(v, [4, 5, 6])
/// }
/// ```
#locals(f)
#alias(map_inplace, deprecated)
pub fn[T] Array::map_in_place(
self : Array[T],
f : (T) -> T raise?,
) -> Unit raise? {
for i, v in self {
self[i] = f(v)
}
}
///|
/// Maps a function over the elements of the array with index.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// let v2 = v.mapi((i, x) => x + i)
/// @test.assert_eq(v2, [3, 5, 7])
/// }
/// ```
#locals(f)
pub fn[T, U] Array::mapi(
self : Array[T],
f : (Int, T) -> U raise?,
) -> Array[U] raise? {
if self.is_empty() {
return []
}
let arr = Array::make_uninit(self.length())
for i, v in self {
arr.unsafe_set(i, f(i, v))
}
arr
}
///|
/// Maps a function over the elements of the array with index in place.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// v.mapi_in_place((i, x) => x + i)
/// @test.assert_eq(v, [3, 5, 7])
/// }
/// ```
#locals(f)
#alias(mapi_inplace, deprecated)
pub fn[T] Array::mapi_in_place(
self : Array[T],
f : (Int, T) -> T raise?,
) -> Unit raise? {
for i, v in self {
self[i] = f(i, v)
}
}
///|
/// Creates a new array containing all elements from the input array that satisfy
/// the given predicate function.
///
/// Parameters:
///
/// * `array` : The array to filter.
/// * `predicate` : A function that takes an element and returns a boolean
/// indicating whether the element should be included in the result.
///
/// Returns a new array containing only the elements for which the predicate
/// function returns `true`. The relative order of the elements is preserved.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// let evens = arr.filter(x => x % 2 == 0)
/// debug_inspect(evens, content="[2, 4]")
/// }
/// ```
#locals(f)
pub fn[T] Array::filter(
self : Array[T],
f : (T) -> Bool raise?,
) -> Array[T] raise? {
let arr = []
for v in self {
if f(v) {
arr.push(v)
}
}
arr
}
///|
/// Tests whether the array contains no elements.
///
/// Parameters:
///
/// * `array` : The array to check.
///
/// Returns `true` if the array has no elements, `false` otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
/// let empty : Array[Int] = []
/// inspect(empty.is_empty(), content="true")
/// let non_empty = [1, 2, 3]
/// inspect(non_empty.is_empty(), content="false")
/// }
/// ```
pub fn[T] Array::is_empty(self : Array[T]) -> Bool {
self.length() == 0
}
///|
/// Reverses the order of elements in an array in place, modifying the original
/// array.
///
/// Parameters:
///
/// * `self` : The array to be reversed.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// arr.rev_in_place()
/// debug_inspect(arr, content="[5, 4, 3, 2, 1]")
/// let arr : Array[Int] = []
/// arr.rev_in_place()
/// debug_inspect(arr, content="[]")
/// }
/// ```
#alias(rev_inplace, deprecated)
pub fn[T] Array::rev_in_place(self : Array[T]) -> Unit {
let len = self.length()
for i in 0..<(len / 2) {
let temp = self.unsafe_get(i)
self.unsafe_set(i, self.unsafe_get(len - i - 1))
self.unsafe_set(len - i - 1, temp)
}
}
///|
/// Creates a new array with elements in reversed order.
///
/// Parameters:
///
/// * `self` : The array to be reversed.
///
/// Returns a new array containing the same elements as the input array but in
/// reverse order. The original array remains unchanged.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// debug_inspect(arr.rev(), content="[5, 4, 3, 2, 1]")
/// debug_inspect(arr, content="[1, 2, 3, 4, 5]") // original array unchanged
/// }
/// ```
pub fn[T] Array::rev(self : Array[T]) -> Array[T] {
let len = self.length()
let arr = Array::make_uninit(len)
for i in 0.. (Array[T], Array[T]) {
if index < 0 || index > self.length() {
let len = self.length()
abort(
"index out of bounds: the len is from 0 to \{len} but the index is \{index}",
)
}
let v1 = Array::unsafe_make_and_blit(
self.buffer(),
allocate_len=index,
len=index,
)
let v2 = if index != self.length() {
let len2 = self.length() - index
Array::unsafe_make_and_blit(
self.buffer(),
allocate_len=len2,
src_offset=index,
len=len2,
)
} else {
Array::make_uninit(0)
}
(v1, v2)
}
///|
/// Checks whether the array contains an element equal to the given value.
///
/// Parameters:
///
/// * `array` : The array to search in.
/// * `value` : The value to search for.
///
/// Returns `true` if the array contains an element equal to the given value,
/// `false` otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// inspect(arr.contains(3), content="true")
/// inspect(arr.contains(6), content="false")
/// let arr : Array[Int] = []
/// inspect(arr.contains(1), content="false")
/// }
/// ```
pub fn[T : Eq] Array::contains(self : Array[T], value : T) -> Bool {
for v in self {
if v == value {
break true
}
} nobreak {
false
}
}
///|
/// Counts how many elements in the array are equal to `value`.
///
/// # Example
/// ```mbt check
/// test {
/// let arr = [1, 2, 1, 3, 1]
/// inspect(arr.count(1), content="3")
/// inspect(arr.count(4), content="0")
/// }
/// ```
pub fn[T : Eq] Array::count(self : Array[T], value : T) -> Int {
self[:].count(value)
}
///|
/// Counts how many elements in the array satisfy the predicate.
///
/// # Example
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// inspect(arr.count_if(x => x % 2 == 0), content="2")
/// }
/// ```
#locals(f)
pub fn[T] Array::count_if(
self : Array[T],
f : (T) -> Bool raise?,
) -> Int raise? {
for v in self; count = 0 {
if f(v) {
continue count + 1
}
continue count
} nobreak {
count
}
}
///|
/// Checks if the array begins with all elements of the provided prefix array in
/// order.
///
/// Parameters:
///
/// * `self` : The array to check against.
/// * `prefix` : The array containing the sequence of elements to look for at the
/// beginning.
///
/// Returns `true` if the array starts with all elements in `prefix` in the same
/// order, `false` otherwise. An empty prefix array always returns `true`, and a
/// prefix longer than the array always returns `false`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// inspect(arr.starts_with([1, 2]), content="true")
/// inspect(arr.starts_with([2, 3]), content="false")
/// inspect(arr.starts_with([]), content="true")
/// inspect(arr.starts_with([1, 2, 3, 4, 5, 6]), content="false")
/// }
/// ```
pub fn[T : Eq] Array::starts_with(
self : Array[T],
prefix : ArrayView[T],
) -> Bool {
self[:].starts_with(prefix)
}
///|
/// Tests if an array ends with the given suffix.
///
/// Parameters:
///
/// * `self` : The array to check.
/// * `suffix` : The array to test against.
///
/// Returns `true` if the array ends with the given suffix, `false` otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// inspect(arr.ends_with([4, 5]), content="true")
/// inspect(arr.ends_with([3, 4]), content="false")
/// inspect(arr.ends_with([]), content="true")
/// let arr : Array[Int] = []
/// inspect(arr.ends_with([]), content="true")
/// inspect(arr.ends_with([1]), content="false")
/// }
/// ```
pub fn[T : Eq] Array::ends_with(self : Array[T], suffix : ArrayView[T]) -> Bool {
self[:].ends_with(suffix)
}
///|
/// Strip a prefix from the array.
///
/// If the array starts with the prefix, return a view of the array after the
/// prefix, otherwise return None. The returned view shares the original
/// backing array — no allocation.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [1, 2, 3, 4, 5]
/// let v2 = v.strip_prefix([1, 2])
/// debug_inspect(
/// v2,
/// content=(
/// #|Some()
/// ),
/// )
/// }
/// ```
pub fn[T : Eq] Array::strip_prefix(
self : Array[T],
prefix : ArrayView[T],
) -> ArrayView[T]? {
self[:].strip_prefix(prefix)
}
///|
/// Strip a suffix from the array.
///
/// If the array ends with the suffix, return a view of the array before the
/// suffix, otherwise return None. The returned view shares the original
/// backing array — no allocation.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// let v2 = v.strip_suffix([5])
/// debug_inspect(
/// v2,
/// content=(
/// #|Some()
/// ),
/// )
/// }
/// ```
pub fn[T : Eq] Array::strip_suffix(
self : Array[T],
suffix : ArrayView[T],
) -> ArrayView[T]? {
self[:].strip_suffix(suffix)
}
///|
/// Searches for the first occurrence of a value in the array and returns its
/// index.
///
/// Parameters:
///
/// * `self` : The array to search in.
/// * `value` : The value to search for.
///
/// Returns an `Option` containing the index of the first occurrence of `value`
/// if found, or `None` if the value is not present in the array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 2, 4]
/// debug_inspect(arr.search(2), content="Some(1)") // first occurrence
/// debug_inspect(arr.search(5), content="None") // not found
/// }
/// ```
pub fn[T : Eq] Array::search(self : Array[T], value : T) -> Int? {
self[:].search(value)
}
///|
/// Search the index of the first element that satisfies the predicate.
///
/// # Example
///
/// ```mbt check
/// test {
/// let v = [1, 2, 3, 4, 5]
/// match v.search_by(x => x == 3) {
/// Some(index) => @test.assert_eq(index, 2) // 2
/// None => println("Not found")
/// }
/// }
/// ```
#locals(f)
#alias(find_index, deprecated)
pub fn[T] Array::search_by(
self : Array[T],
f : (T) -> Bool raise?,
) -> Int? raise? {
for i, v in self {
if f(v) {
break Some(i)
}
} nobreak {
None
}
}
///|
/// Performs a binary search on a sorted array to find the index of a given element.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// let result = v.binary_search(3)
/// @test.assert_eq(result, Ok(0)) // The element 3 is found at index 0
/// }
/// ```
///
/// # Arguments
/// - `self`: The array in which to perform the search.
/// - `value`: The element to search for in the array.
///
/// # Returns
/// - `Result[Int, Int]`:
/// If the element is found, an `Ok` variant is returned, containing the index of the matching element in the array.
/// If there are multiple matches, the leftmost match will be returned.
/// If the element is not found, an `Err` variant is returned, containing the index where the element could be inserted to maintain the sorted order.
///
/// # Notes
/// - Ensure that the array is sorted in increasing order before calling this function.
/// - If the array is not sorted, the returned result is undefined and should not be relied on.
pub fn[T : Compare] Array::binary_search(
self : Array[T],
value : T,
) -> Result[Int, Int] {
self[:].binary_search(value)
}
///|
/// Performs a binary search on a sorted array using a custom comparison
/// function. Returns the position of the matching element if found, or the
/// position where the element could be inserted while maintaining the sorted
/// order.
///
/// Parameters:
///
/// * `array` : The sorted array to search in.
/// * `comparator` : A function that compares each element with the target value,
/// returning:
/// * A negative integer if the element is less than the target
/// * Zero if the element equals the target
/// * A positive integer if the element is greater than the target
///
/// Returns a `Result` containing either:
///
/// * `Ok(index)` if a matching element is found at position `index`
/// * `Err(index)` if no match is found, where `index` is the position where the
/// element could be inserted
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 3, 5, 7, 9]
/// let find_3 = arr.binary_search_by(x => x.compare(3))
/// debug_inspect(find_3, content="Ok(1)")
/// let find_4 = arr.binary_search_by(x => x.compare(4))
/// debug_inspect(find_4, content="Err(2)")
/// }
/// ```
///
/// Notes:
///
/// * Assumes the array is sorted according to the ordering implied by the
/// comparison function
/// * For multiple matches, returns the leftmost matching position
/// * Returns an insertion point that maintains the sort order when no match is
/// found
pub fn[T] Array::binary_search_by(
self : Array[T],
cmp : (T) -> Int raise?,
) -> Result[Int, Int] raise? {
self[:].binary_search_by(cmp)
}
///|
/// Swaps the values at two positions in the array.
///
/// Parameters:
///
/// * `array` : The array in which to swap elements.
/// * `index1` : The index of the first element to be swapped.
/// * `index2` : The index of the second element to be swapped.
///
/// This function will panic if either index is negative or greater than or equal to
/// the length of the array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// arr.swap(0, 2)
/// debug_inspect(arr, content="[3, 2, 1]")
/// }
/// ```
pub fn[T] Array::swap(self : Array[T], i : Int, j : Int) -> Unit {
if i >= self.length() || j >= self.length() || i < 0 || j < 0 {
index_out_of_bounds2(self.length(), i, j)
}
let temp = self.unsafe_get(i)
self.unsafe_set(i, self.unsafe_get(j))
self.unsafe_set(j, temp)
}
///|
/// Removes all elements from the array that do not satisfy the predicate
/// function, modifying the array in place. The order of remaining elements is
/// preserved.
///
/// Parameters:
///
/// * `array` : The array to be filtered.
/// * `predicate` : A function that takes an element and returns `true` if the
/// element should be kept, `false` if it should be removed.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// arr.retain(x => x % 2 == 0)
/// debug_inspect(arr, content="[2, 4]")
/// let arr = [1, 2, 3]
/// arr.retain(x => x > 10)
/// debug_inspect(arr, content="[]")
/// let arr = [1, 2, 3]
/// arr.retain(_ => true)
/// debug_inspect(arr, content="[1, 2, 3]")
/// }
/// ```
#locals(f)
pub fn[T] Array::retain(self : Array[T], f : (T) -> Bool raise?) -> Unit raise? {
let len = self.length()
let write = for read, v in self; write = 0 {
if f(v) {
if read != write {
self.unsafe_set(write, v)
}
continue write + 1
}
continue write
} nobreak {
write
}
if write != len {
self.unsafe_truncate_to_length(write)
}
}
///|
/// Resizes an array to a specified length, either by truncating if the new
/// length is smaller, or by appending copies of a default value if the new
/// length is larger.
///
/// Parameters:
///
/// * `array` : The array to be resized.
/// * `new_length` : The desired length of the array after resizing.
/// * `default_value` : The value to append when extending the array.
///
/// Throws a panic if `new_length` is negative.
///
/// Examples:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// arr.resize(3, 0)
/// debug_inspect(arr, content="[1, 2, 3]")
/// let arr = [1, 2, 3]
/// arr.resize(5, 0)
/// debug_inspect(arr, content="[1, 2, 3, 0, 0]")
/// }
/// ```
///
pub fn[T] Array::resize(self : Array[T], new_len : Int, f : T) -> Unit {
if new_len < 0 {
abort("negative new length")
}
let len = self.length()
if new_len < len {
self.unsafe_truncate_to_length(new_len)
} else if new_len > len {
self.unsafe_resize_with_default(new_len, f)
}
}
///|
/// Flattens an array of arrays into an array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let v = [[3, 4], [5, 6]].flatten()
/// @test.assert_eq(v, [3, 4, 5, 6])
/// }
/// ```
pub fn[T] Array::flatten(self : Array[Array[T]]) -> Array[T] {
let len = for x in self; len = 0 {
continue len + x.length()
} nobreak {
len
}
let res = Array::make_uninit(len)
for xs in self; i = 0 {
res.unsafe_blit(i, xs, 0, xs.length())
continue i + xs.length()
}
res
}
///|
/// Create an array by repeating `self` `times` times.
///
/// Aborts if `times` is negative. When `times` is `0`, returns an empty array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let v = [3, 4].repeat(2)
/// @test.assert_eq(v, [3, 4, 3, 4])
/// }
/// ```
pub fn[T] Array::repeat(self : Array[T], times : Int) -> Array[T] {
if times < 0 {
abort("negative repeat count")
}
let len = self.length()
if times == 0 || len == 0 {
return []
}
let total = len * times
guard total / times == len else { abort("repeat result too large") }
let v = Array::new(capacity=total)
for _ in 0.. sum + elem)
/// @test.assert_eq(sum, 15)
/// }
/// ```
#locals(f)
#alias(fold_left, deprecated)
pub fn[A, B] Array::fold(
self : Array[A],
init~ : B,
f : (B, A) -> B raise?,
) -> B raise? {
for item in self; acc = init {
continue f(acc, item)
} nobreak {
acc
}
}
///|
/// Fold out values from an array according to certain rules in reversed turn.
///
/// Example:
///
/// ```mbt check
/// test {
/// let sum = [1, 2, 3, 4, 5].rev_fold(init=0, (sum, elem) => sum + elem)
/// @test.assert_eq(sum, 15)
/// }
/// ```
#locals(f)
#alias(fold_right, deprecated)
pub fn[A, B] Array::rev_fold(
self : Array[A],
init~ : B,
f : (B, A) -> B raise?,
) -> B raise? {
for i = self.length() - 1, acc = init; i >= 0; {
continue i - 1, f(acc, self[i])
} nobreak {
acc
}
}
///|
/// Fold out values from an array according to certain rules with index.
///
/// Example:
///
/// ```mbt check
/// test {
/// let sum = [1, 2, 3, 4, 5].foldi(init=0, (index, sum, _elem) => sum + index)
/// @test.assert_eq(sum, 10)
/// }
/// ```
#locals(f)
#alias(fold_lefti, deprecated)
pub fn[A, B] Array::foldi(
self : Array[A],
init~ : B,
f : (Int, B, A) -> B raise?,
) -> B raise? {
for i, x in self; acc = init {
continue f(i, acc, x)
} nobreak {
acc
}
}
///|
/// Fold out values from an array according to certain rules in reversed turn with index.
///
/// Example:
///
/// ```mbt check
/// test {
/// let sum = [1, 2, 3, 4, 5].rev_foldi(init=0, (index, sum, _elem) => sum + index)
/// @test.assert_eq(sum, 10)
/// }
/// ```
#locals(f)
#alias(fold_righti, deprecated)
pub fn[A, B] Array::rev_foldi(
self : Array[A],
init~ : B,
f : (Int, B, A) -> B raise?,
) -> B raise? {
let len = self.length()
for i in len>..0; acc = init {
continue f(len - i - 1, acc, self[i])
} nobreak {
acc
}
}
///|
/// Removes consecutive duplicate elements from an array in-place, using equality
/// comparison. The first occurrence of each element is retained while subsequent
/// equal elements are removed.
///
/// Parameters:
///
/// * `array` : The array to remove duplicates from. Must contain elements that
/// implement the `Eq` trait for equality comparison.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 2, 3, 3, 3, 2]
/// arr.dedup()
/// debug_inspect(arr, content="[1, 2, 3, 2]")
/// let arr = [1, 2, 2, 2, 3, 3]
/// arr.dedup()
/// debug_inspect(arr, content="[1, 2, 3]")
/// let arr : Array[Int] = []
/// arr.dedup()
/// debug_inspect(arr, content="[]")
/// }
/// ```
///
/// Note: For best results when removing all duplicates regardless of position,
/// sort the array before calling this function. When used on an unsorted array,
/// this function only removes consecutive duplicates.
pub fn[T : Eq] Array::dedup(self : Array[T]) -> Unit {
if self.is_empty() {
return
}
let w = for i in 1.. x % 2 == 0)
/// debug_inspect(extracted, content="[2, 4]")
/// debug_inspect(arr, content="[1, 3, 5]")
/// }
/// ```
#locals(f)
pub fn[T] Array::extract_if(
self : Array[T],
f : (T) -> Bool raise?,
) -> Array[T] raise? {
let removed = []
for read in 0.., , ]
/// ),
/// )
/// let arr : Array[Int] = []
/// debug_inspect(arr.chunks(3), content="[]")
/// }
/// ```
pub fn[T] Array::chunks(self : Array[T], size : Int) -> Array[ArrayView[T]] {
guard! size > 0
let len = self.length()
if len == 0 {
return []
}
let num_chunks = (len + size - 1) / size
Array::makei(num_chunks, i => {
let start = i * size
let end = Int::min(start + size, len)
self[start:end]
})
}
///|
/// Groups consecutive elements of the array into chunks where adjacent elements
/// satisfy the given predicate function.
///
/// Parameters:
///
/// * `array` : The array to be chunked.
/// * `predicate` : A function that takes two adjacent elements and returns
/// `true` if they should be in the same chunk, `false` otherwise.
///
/// Returns an array of arrays, where each inner array is a chunk of consecutive
/// elements that satisfy the predicate with their adjacent elements.
///
/// Example:
///
/// ```mbt check
/// test {
/// let v = [1, 1, 2, 3, 2, 3, 2, 3, 4]
/// let chunks = v.chunk_by((x, y) => x <= y)
/// debug_inspect(
/// chunks,
/// content=(
/// #|[
/// #| ,
/// #| ,
/// #| ,
/// #|]
/// ),
/// )
/// let v : Array[Int] = []
/// debug_inspect(v.chunk_by((x, y) => x <= y), content="[]")
/// }
/// ```
#locals(pred)
pub fn[T] Array::chunk_by(
self : Array[T],
pred : (T, T) -> Bool raise?,
) -> Array[ArrayView[T]] raise? {
let chunks = []
if self.is_empty() {
return chunks
}
let start = for i in 1..,
/// #| ,
/// #| ,
/// #| ,
/// #|]
/// ),
/// )
/// let arr = [1, 2]
/// debug_inspect(arr.windows(3), content="[]")
/// }
/// ```
pub fn[T] Array::windows(self : Array[T], size : Int) -> Array[ArrayView[T]] {
guard! size > 0
let len = self.length() - size + 1
if len < 1 {
return []
}
Array::makei(len, i => self[i:i + size])
}
///|
/// Return an iterator over all suffix views of this array.
///
/// Suffixes are yielded from longest to shortest. Set `include_empty=true` to
/// include the final empty suffix.
///
/// Example:
///
/// ```mbt check
/// test {
/// let xs = [1, 2]
/// debug_inspect(
/// xs.suffixes().collect(),
/// content=(
/// #|[, ]
/// ),
/// )
/// debug_inspect(
/// xs.suffixes(include_empty=true).collect(),
/// content=(
/// #|[, , ]
/// ),
/// )
/// }
/// ```
pub fn[T] Array::suffixes(
self : Array[T],
include_empty? : Bool = false,
) -> Iter[ArrayView[T]] {
self[:].suffixes(include_empty~)
}
///|
/// Splits an array into chunks using a predicate function. Creates chunks by
/// grouping consecutive elements that do not satisfy the predicate function.
/// Elements that satisfy the predicate function are excluded from the resulting
/// chunks and act as delimiters.
///
/// Parameters:
///
/// * `array` : The array to be split into chunks.
/// * `predicate` : A function that takes an element and returns `true` if the
/// element should be used as a delimiter.
///
/// Returns an array of arrays, where each inner array is a chunk of consecutive
/// elements that do not satisfy the predicate.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 0, 2, 0, 3, 0, 4]
/// debug_inspect(arr.split(x => x == 0), content="[[1], [2], [3], [4]]")
/// let arr = [0, 1, 0, 0, 2, 0]
/// debug_inspect(arr.split(x => x == 0), content="[[], [1], [], [2]]")
/// }
/// ```
#locals(pred)
pub fn[T] Array::split(
self : Array[T],
pred : (T) -> Bool raise?,
) -> Array[Array[T]] raise? {
let chunks = []
for i = 0; i < self.length(); {
let chunk = []
let i = for i = i; i < self.length() && !pred(self[i]); {
chunk.push(self[i])
continue i + 1
} nobreak {
i
}
chunks.push(chunk)
continue i + 1
}
chunks
}
///|
/// Creates an iterator over the elements of the array.
///
/// Parameters:
///
/// * `array` : The array to create an iterator from.
///
/// Returns an iterator that yields each element of the array in order.
/// This iterator is created from `self[:]`, so the traversal bounds are fixed
/// when iteration starts.
///
/// Structural mutations after the iterator is created are unsupported.
/// Appended elements are not visited, and shrinking the array with operations
/// such as `remove`, `truncate`, `clear`, or `drain` may cause later iterator
/// steps to fail. The same caveat applies to `rev_iter()`, `iter2()`, and
/// helpers built on top of them such as `each()`, `eachi()`, and `fold()`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// let mut sum = 0
/// arr.iter().each(x => sum += x)
/// inspect(sum, content="6")
/// }
/// ```
#alias(iterator, deprecated)
pub fn[T] Array::iter(self : Array[T]) -> Iter[T] {
self[:].iter()
}
///|
/// Returns an iterator that yields elements from the array in reverse order,
/// from the last element to the first.
///
/// Parameters:
///
/// * `array` : The array to iterate over in reverse order.
///
/// Returns an iterator that yields each element of the array, starting from the
/// last element and moving towards the first.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// let result = []
/// arr.rev_iter().each(x => result.push(x))
/// debug_inspect(result, content="[3, 2, 1]")
/// }
/// ```
#alias(rev_iterator, deprecated)
pub fn[T] Array::rev_iter(self : Array[T]) -> Iter[T] {
self[:].rev_iter()
}
///|
/// Returns an iterator that provides both indices and values of the array in
/// order.
///
/// Parameters:
///
/// * `self` : The array to iterate over.
///
/// Returns an iterator that yields tuples of index and value pairs, where
/// indices start from 0.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [10, 20, 30]
/// let mut sum = 0
/// arr.iter2().each((i, x) => sum += i + x)
/// inspect(sum, content="63") // (0 + 10) + (1 + 20) + (2 + 30) = 63
/// }
/// ```
#alias(iterator2, deprecated)
pub fn[A] Array::iter2(self : Array[A]) -> Iter2[Int, A] {
self[:].iter2()
}
///|
/// Creates a new empty array.
///
/// Returns an empty array of type `Array[T]`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr : Array[Int] = []
/// inspect(arr.length(), content="0")
/// inspect(arr.is_empty(), content="true")
/// }
/// ```
pub impl[T] Default for Array[T] with fn default() {
[]
}
///|
/// Removes a back element from an array.
///
/// # Example
/// ```mbt check
/// test {
/// let array = [1, 2, 3, 4, 5]
/// array.unsafe_pop_back()
/// @test.assert_eq(array.last(), Some(4))
/// }
/// ```
#internal(unsafe, "Panic if the array is empty on non-JS backend.")
#doc(hidden)
pub fn[A] Array::unsafe_pop_back(self : Array[A]) -> Unit {
self.unsafe_pop() |> ignore
}
///|
/// Truncates the array in-place to the specified length.
///
/// If `len` is greater than or equal to the current array length,
/// the function does nothing. If `len` is 0, the array is cleared.
/// Otherwise, removes elements from the end until the array reaches the given length.
///
/// Parameters:
///
/// * `self` : The target array (modified in-place).
/// * `len` : The new desired length (must be non-negative).
///
/// Important:
/// - If `len` is negative, the function does nothing.
/// - If `len` exceeds current length, the array remains unchanged.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// arr.truncate(3)
/// debug_inspect(arr, content="[1, 2, 3]")
/// }
/// ```
pub fn[A] Array::truncate(self : Array[A], len : Int) -> Unit {
guard len >= 0 && len < self.length() else { return }
self.unsafe_truncate_to_length(len)
}
///|
/// In-place filter and map for Array
///
/// # Example
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// arr.retain_map(fn(x) { if x % 2 == 0 { Some(x * 2) } else { None } })
/// debug_inspect(arr, content="[4, 8]")
/// }
/// ```
#locals(f)
pub fn[A] Array::retain_map(
self : Array[A],
f : (A) -> A? raise?,
) -> Unit raise? {
if self.is_empty() {
return
}
let buf = self.buffer()
let len = self.length()
for read_idx in 0.. {
buf[write_idx] = new_val
continue write_idx + 1
}
None => continue write_idx
}
} nobreak {
self.unsafe_truncate_to_length(write_idx)
}
}
///|
/// Creates a new array containing all elements from an iterator.
///
/// Parameters:
///
/// * `iterator` : An iterator containing elements of type `T`.
///
/// Returns a new array containing all elements from the iterator in the same
/// order.
///
/// Example:
///
/// ```mbt check
/// test {
/// let iter = Iter::singleton(42)
/// let arr = Array::from_iter(iter)
/// debug_inspect(arr, content="[42]")
/// }
/// ```
#alias(from_iterator, deprecated)
pub fn[T] Array::from_iter(iter : Iter[T]) -> Array[T] {
iter.collect()
}
///|
/// Adds all elements from an iterator to the end of the array.
///
/// This function iterates over each element in the provided iterator
/// and adds them to the array using the `push` method.
///
/// # Example
/// ```mbt check
/// test {
/// let u = [1, 2, 3]
/// let v = [4, 5, 6]
/// u.push_iter(v.iter())
/// @test.assert_eq(u, [1, 2, 3, 4, 5, 6])
/// }
/// ```
pub fn[T] Array::push_iter(self : Self[T], iter : Iter[T]) -> Unit {
// This function used by [Array spread operator](https://docs.moonbitlang.com/en/latest/language/fundamentals.html#spread-operator)
// it can't be removed and deprecated
if iter.size_hint() is Some(n) {
self.reserve_capacity(self.length() + n)
}
for x in iter {
self.push(x)
}
}
///|
/// Shuffle the array using Knuth shuffle
///
/// To use this function, you need to provide a rand function, which takes an integer as it upper bound
/// and returns an integer.
/// *rand n* is expected to returns a uniformly distribution integer between 0 and n - 1
/// # Example
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3, 4, 5]
/// fn rand(upper : Int) -> Int {
/// let rng = @random.Rand::new()
/// rng.int(limit=upper)
/// }
///
/// Array::shuffle_in_place(arr, rand~)
/// }
/// ```
#locals(rand)
pub fn[T] Array::shuffle_in_place(
self : Array[T],
rand~ : (Int) -> Int,
) -> Unit {
let n = self.length()
for i in n>..1 {
let j = rand(i + 1) % (i + 1)
// for safety, perf is not a concern here
// TODO: maybe return an error later
self.swap(i, j)
}
}
///|
/// Shuffle the array using Knuth shuffle
///
/// To use this function, you need to provide a rand function, which takes an integer as it upper bound
/// and returns an integer.
/// *rand n* is expected to returns a uniformly distribution integer between 0 and n - 1
/// # Example
///
/// ```mbt nocheck
/// let arr = [1, 2, 3, 4, 5]
///
/// fn rand(upper : Int) -> Int {
/// let rng = @random.Rand::new()
/// rng.int(limit=upper)
/// }
///
/// let _shuffled = Array::shuffle(arr, rand~)
/// ```
pub fn[T] Array::shuffle(self : Array[T], rand~ : (Int) -> Int) -> Array[T] {
let new_arr = self.copy()
Array::shuffle_in_place(new_arr, rand~)
new_arr
}
///|
/// Returns a new array containing the elements of the original array that satisfy the given predicate.
///
/// # Arguments
///
/// * `self` - The array to filter.
/// * `f` - The predicate function.
///
/// # Returns
///
#locals(f)
pub fn[A, B] Array::filter_map(
self : Array[A],
f : (A) -> B? raise?,
) -> Array[B] raise? {
let result = []
for x in self {
if f(x) is Some(x) {
result.push(x)
}
}
result
}
///|
/// Returns the last element of the array, or `None` if the array is empty.
///
/// Parameters:
///
/// * `array` : The array to get the last element from.
///
/// Returns an optional value containing the last element of the array. The
/// result is `None` if the array is empty, or `Some(x)` where `x` is the last
/// element of the array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// debug_inspect(arr.last(), content="Some(3)")
/// let empty : Array[Int] = []
/// debug_inspect(empty.last(), content="None")
/// }
/// ```
pub fn[A] Array::last(self : Array[A]) -> A? {
match self {
[] => None
[.., last] => Some(last)
}
}
///|
/// Zips two arrays into a single array of tuples.
///
/// Parameters:
///
/// * `self` : The first array.
/// * `other` : The second array.
///
/// Returns an array of tuples, where each tuple contains corresponding elements
/// from the two input arrays.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr1 = [1, 2, 3]
/// let arr2 = ['a', 'b', 'c']
/// debug_inspect(arr1.zip(arr2), content="[(1, 'a'), (2, 'b'), (3, 'c')]")
/// }
/// ```
pub fn[A, B] Array::zip(self : Array[A], other : Array[B]) -> Array[(A, B)] {
let length = if self.length() < other.length() {
self.length()
} else {
other.length()
}
Array::makei(length, i => (self[i], other[i]))
}
///|
/// Splits an array of pairs into two arrays, separating the first and second elements.
///
/// # Example
/// ```mbt check
/// test {
/// let arr = [(1, "a"), (2, "b"), (3, "c")]
/// let (nums, strs) = arr.unzip()
/// debug_inspect(nums, content="[1, 2, 3]")
/// debug_inspect(strs, content="[\"a\", \"b\", \"c\"]")
/// }
/// ```
pub fn[T1, T2] Array::unzip(self : Array[(T1, T2)]) -> (Array[T1], Array[T2]) {
let arr1 : Array[T1] = Array::new(capacity=self.length())
let arr2 : Array[T2] = Array::new(capacity=self.length())
for pair in self {
let (x, y) = pair
arr1.push(x)
arr2.push(y)
}
(arr1, arr2)
}
///|
/// Zips two arrays into an iterator that yields corresponding elements.
///
/// Parameters:
///
/// * `self` : The first array.
/// * `other` : The second array.
///
/// Returns an `Iter2` iterator that produces corresponding elements
/// from both arrays. The iteration continues until the shorter array is exhausted.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr1 = [1, 2, 3]
/// let arr2 = ['a', 'b', 'c']
/// debug_inspect(
/// arr1.zip_to_iter2(arr2).to_array(),
/// content="[(1, 'a'), (2, 'b'), (3, 'c')]",
/// )
/// }
/// ```
pub fn[A, B] Array::zip_to_iter2(
self : Array[A],
other : Array[B],
) -> Iter2[A, B] {
let length = if self.length() < other.length() {
self.length()
} else {
other.length()
}
let mut i = 0
Iter2::new(
() => {
guard i < length else { None }
let elem = (self[i], other[i])
i += 1
Some(elem)
},
size_hint=length,
).iter2()
}
///|
/// Join an array of strings using the provided `separator`.
///
/// Parameters:
/// * `separator` : The string inserted between each element.
///
/// Returns a single concatenated `String`.
/// # Example:
/// ```mbt check
/// test {
/// let s = "hello world"
/// inspect(s.split(" ").to_array().join(":"), content="hello:world")
/// }
/// ```
pub fn[A : ToStringView] Array::join(
self : Array[A],
separator : StringView,
) -> String {
self[:].join(separator)
}
///|
/// Performs a lexicographical comparison of two arrays.
///
/// This method compares the arrays element by element until a difference is
/// found or one array is exhausted. Unlike the `Compare` trait implementation
/// which uses shortlex order (shorter arrays come first), this method compares
/// based purely on element values until a difference is found.
///
/// # Returns
///
/// - A negative integer if `self` is lexicographically less than `other`
/// - Zero if `self` is lexicographically equal to `other`
/// - A positive integer if `self` is lexicographically greater than `other`
///
/// # Example
///
/// ```mbt check
/// test {
/// inspect([1, 2].lexical_compare([1, 2, 3]), content="-1")
/// inspect([1, 2, 3].lexical_compare([1, 2]), content="1")
/// inspect([1, 2, 3].lexical_compare([1, 2, 3]), content="0")
/// inspect([1, 2, 3].lexical_compare([1, 2, 4]), content="-1")
/// }
/// ```
pub fn[T : Compare] Array::lexical_compare(
self : Array[T],
other : Array[T],
) -> Int {
self[:].lexical_compare(other)
}