// 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.
///|
/// An `Array` is a collection of values that supports random access and can
/// grow in size.
struct Array[T] {
mut buf : UninitializedArray[T]
mut len : Int32
}
///|
/// Creates an array of `len` elements whose contents are unspecified. The
/// array reports `length() == len` immediately, so every slot is observable
/// and the caller must write all of them before the array is read or escapes.
///
/// Prefer `Array::makei` unless the initializing loop cannot be expressed as
/// a function of the index.
#doc(hidden)
pub fn[T] Array::unsafe_make_uninit(len : Int32) -> Array[T] {
{ buf: UninitializedArray::make(len), len, }
}
///|
#owned(src)
fn[T] Array::unsafe_make_and_blit(
src : UninitializedArray[T],
allocate_len~ : Int32,
len~ : Int32,
src_offset~ : Int32 = 0,
dst_offset~ : Int32 = 0,
) -> Array[T] {
{
buf: UninitializedArray::make_and_blit(
src,
allocate_len~,
src_offset~,
dst_offset~,
len~,
),
len: allocate_len,
}
}
///|
#owned(src)
fn[T] Array::unsafe_make_and_blit_from_fixed(
src : FixedArray[T],
allocate_len~ : Int32,
len~ : Int32,
src_offset~ : Int32 = 0,
dst_offset~ : Int32 = 0,
) -> Array[T] {
{
buf: UninitializedArray::unsafe_make_and_blit_from_fixed(
src, allocate_len, src_offset, dst_offset, len,
),
len: allocate_len,
}
}
///|
#owned(value)
fn[T] Array::unsafe_resize_with_default(
self : Array[T],
new_len : Int32,
value : T,
) -> Unit {
let len = self.length()
guard! new_len >= len
if new_len <= self.capacity() {
self.buf.unchecked_fill(len, value, new_len - len)
} else {
let new_buf = UninitializedArray::unsafe_make_and_blit_with_init(
self.buf,
new_len,
value,
0,
0,
len,
)
self.buf = new_buf
}
self.len = new_len
}
///|
/// Creates a new empty array with an optional initial capacity.
///
/// Parameters:
///
/// * `capacity` : The initial capacity of the array. If 0 (default), creates an
/// array with minimum capacity. Must be non-negative.
///
/// Returns a new empty array of type `Array[T]` with the specified initial
/// capacity.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr : Array[Int32] = Array(capacity=10)
/// inspect(arr.length(), content="0")
/// inspect(arr.capacity(), content="10")
/// let arr : Array[Int32] = Array()
/// inspect(arr.length(), content="0")
/// }
/// ```
pub fn[T] Array::Array(capacity~ : Int32 = 0) -> Array[T] {
if capacity == 0 {
[]
} else {
{ buf: UninitializedArray::make(capacity), len: 0, }
}
}
///|
/// Returns the number of elements in the array.
///
/// Parameters:
///
/// * `array` : The array whose length is to be determined.
///
/// Returns the number of elements in the array as an integer.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr : Array[Int32] = [1, 2, 3]
/// inspect(arr.length(), content="3")
/// let empty : Array[Int32] = []
/// inspect(empty.length(), content="0")
/// }
/// ```
#intrinsic("%array.length")
pub fn[T] Array::length(self : Array[T]) -> Int32 {
self.len
}
///|
/// Truncates the array to the specified length. This function is marked as
/// `unsafe` because it directly manipulates the internal buffer of the array,
/// which can lead to undefined behavior if not used carefully.
///
/// # Parameters
///
/// - `self` : The array to be truncated.
/// - `new_len` : The new length to which the array should be truncated. Must be
/// less than or equal to the current length of the array.
///
/// # Returns
///
/// - `Unit` : This function does not return a value.
///
/// # Errors
///
/// - This function does not raise errors, but it panics if `new_len` is greater
/// than the current length of the array.
///
/// # Retention
///
/// The slots beyond `new_len` are left as they are rather than nulled out, so
/// that an `ArrayView` created before the call can never observe uninitialized
/// memory. The removed elements stay reachable from the buffer until those
/// slots are reused by later pushes, until the buffer grows, or until the
/// array is dropped; `Array::release_unused` overwrites them on demand.
fn[T] Array::unsafe_truncate_to_length(
self : Array[T],
new_len : Int32,
) -> Unit {
guard! new_len <= self.length()
self.len = new_len
}
///|
test "unsafe_truncate_to_length" {
let arr = [1, 2, 3, 4, 5]
arr.unsafe_truncate_to_length(3)
assert_true(arr == [1, 2, 3])
}
///|
fn[T] Array::buffer(self : Array[T]) -> UninitializedArray[T] {
self.buf
}
///|
/// Compute the next capacity without allocating. Since arrays never shrink
/// while growing, `required < len` means the required-size calculation
/// overflowed.
#inline
fn array_growth_capacity(
current : Int32,
len : Int32,
required : Int32,
) -> Int32 {
if required < len {
abort("Array capacity overflow")
}
let start = if current == 0 { 8 } else { current }
for space = start; space < required; {
let next = space * 2
if next <= space {
break required
}
continue next
} nobreak {
space
}
}
///|
test "Array growth capacity doubles while representable" {
inspect(array_growth_capacity(8, 8, 9), content="16")
inspect(array_growth_capacity(8, 8, 33), content="64")
}
///|
test "Array growth capacity falls back to the exact requirement" {
inspect(
array_growth_capacity(0x40000000, 0x40000000, 0x40000001),
content="1073741825",
)
inspect(
array_growth_capacity(0x20000000, 0x20000000, 0x40000001),
content="1073741825",
)
}
///|
test "panic Array growth rejects a wrapped required size" {
ignore(array_growth_capacity(16, 16, -1))
}
///|
fn[T] Array::resize_buffer(self : Array[T], new_capacity : Int32) -> Unit {
let old_buf = self.buf
// Drop the array's reference so make_and_blit can see a unique old buffer.
// On native/Wasm1 this lets it transfer element references without retaining
// each one. Other owners still keep the buffer on the shared-copy path.
self.buf = []
// Only the live prefix is worth carrying over. The slots beyond `len` hold
// either NULL or the elements earlier removals left behind, and leaving them
// with the old buffer is what releases the latter when that buffer dies.
let len = self.len
let copy_len = if len < new_capacity { len } else { new_capacity }
let new_buf = UninitializedArray::make_and_blit(
old_buf,
allocate_len=new_capacity,
len=copy_len,
)
self.buf = new_buf
}
///|
test "UninitializedArray::unsafe_blit_fixed" {
let src = FixedArray::make(5, 0)
let dst = UninitializedArray::make(5)
for i in 0..<5 {
src[i] = i + 1
}
UninitializedArray::unsafe_blit_fixed(dst, 0, src, 0, 5)
for i in 0..<5 {
assert_true(dst[i] == src[i])
}
}
///|
test "Array::resize_buffer" {
let arr = Array::Array(capacity=2)
arr.push(1)
arr.push(2)
arr.resize_buffer(4)
assert_true(arr.buffer().0.length() >= 4)
arr.push(3)
arr.push(4)
assert_true(arr.length() == 4)
assert_true(arr[0] == 1)
assert_true(arr[1] == 2)
assert_true(arr[2] == 3)
assert_true(arr[3] == 4)
}
///|
/// Reallocate the array to fit `required`. Callers keep the capacity check on
/// their fast path and only call this when allocation is necessary.
#inline(never)
fn[T] Array::realloc(self : Array[T], required : Int32) -> Unit {
let old_cap = self.capacity()
let new_cap = array_growth_capacity(old_cap, self.length(), required)
self.resize_buffer(new_cap)
}
///|
/// Reserves capacity to ensure that it can hold at least the number of elements
/// specified by the `capacity` argument.
///
/// # Example
///
/// ```mbt check
/// test {
/// let v = [1]
/// v.reserve_capacity(10)
/// @test.assert_eq(v.capacity(), 10)
/// }
/// ```
pub fn[T] Array::reserve_capacity(self : Array[T], capacity : Int32) -> Unit {
if self.capacity() >= capacity {
return
}
self.resize_buffer(capacity)
}
///|
/// Shrinks the capacity of the array as much as possible.
///
/// # Example
///
/// ```mbt check
/// test {
/// let v = Array(capacity=10)
/// v.push(1)
/// v.push(2)
/// v.push(3)
/// v.shrink_to_fit()
/// @test.assert_eq(v.capacity(), 3)
/// }
/// ```
///
/// The survivors are copied into the new buffer and the old one is released
/// with them, so this also releases whatever earlier removals left in the
/// unused capacity. It pays an allocation plus a copy of every survivor to do
/// so; `Array::release_unused` releases the same elements in one pass over the
/// unused region and no allocation, at the cost of leaving the capacity alone.
pub fn[T] Array::shrink_to_fit(self : Array[T]) -> Unit {
if self.capacity() <= self.length() {
return
}
self.resize_buffer(self.length())
}
///|
/// Overwrites the array's unused capacity -- every slot from `length()` up to
/// `capacity()` -- with `placeholder`, releasing whatever those slots held.
///
/// Shrinking an array never clears the slots it vacates, so whatever they held
/// -- a removed element, or a duplicate reference to a survivor that was
/// shifted over it -- stays reachable from the buffer and unreleased until
/// later pushes reuse those slots, the buffer grows, or the array is dropped.
/// This releases them on demand without reallocating, which is what
/// `Array::pop`, `Array::remove`, `Array::retain` and the other operations
/// that take no placeholder leave outstanding. `Array::shrink_to_fit` releases
/// them too, but by allocating an exact-size buffer and copying every survivor
/// into it; this costs one pass over the unused region and no allocation.
///
/// An `ArrayView` created before the call observes `placeholder` in that region
/// afterwards, in place of whatever it held.
///
/// This only matters for element types holding references -- for types such as
/// `Int` there is nothing to release and the call merely costs a pass over the
/// buffer.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = ["a", "b", "c"]
/// let _ = arr.pop()
/// arr.release_unused(placeholder="")
/// debug_inspect(arr, content="[\"a\", \"b\"]")
/// }
/// ```
#owned(placeholder)
pub fn[T] Array::release_unused(self : Array[T], placeholder~ : T) -> Unit {
let len = self.len
self.buf.unchecked_fill(len, placeholder, self.capacity() - len)
}
///|
/// Adds an element to the end of the array.
///
/// If the array is at capacity, it will be reallocated.
///
/// # Example
/// ```mbt check
/// test {
/// let v = []
/// v.push(3)
/// }
/// ```
#owned(value)
pub fn[T] Array::push(self : Array[T], value : T) -> Unit {
if self.length() == self.buffer().0.length() {
self.realloc(self.length() + 1)
}
let length = self.length()
self.unsafe_set(length, value)
self.len = length + 1
}
///|
/// Appends all elements from one array to the end of another array. The elements
/// are added in-place, modifying the original array.
///
/// Parameters:
///
/// * `self` : The array to append to.
/// * `other` : The array whose elements will be appended.
///
/// Example:
///
/// ```mbt check
/// test {
/// let v1 = [1, 2, 3]
/// let v2 : ReadOnlyArray[Int32] = [4, 5, 6]
/// v1.append(v2)
/// debug_inspect(v1, content="[1, 2, 3, 4, 5, 6]")
/// let v1 = [1, 2, 3]
/// let v2 : ReadOnlyArray[Int32] = []
/// v1.append(v2)
/// debug_inspect(v1, content="[1, 2, 3]")
/// }
/// ```
pub fn[T] Array::append(self : Array[T], other : ArrayView[T]) -> Unit {
let src = other.buf()
let src_offset = other.start()
let append_len = other.length()
let old_len = self.len
let new_len = old_len + append_len
guard! new_len >= old_len
if new_len > self.buf.0.length() {
self.realloc(new_len)
}
self.len = new_len
UninitializedArray::unsafe_blit(
self.buf,
old_len,
src,
src_offset,
append_len,
)
}
///|
/// Copies elements from one array to another array, with support for growing the
/// destination array if needed. The arrays may overlap, in which case the copy
/// is performed in a way that preserves the data.
///
/// Parameters:
///
/// * `self` : The array to copy elements from.
/// * `dst` : The array to copy elements to. Will be automatically grown
/// if needed to accommodate the copied elements.
/// * `len` : The number of elements to copy.
/// * `src_offset` : Starting index in the source array. Defaults to 0.
/// * `dst_offset` : Starting index in the destination array. Defaults to
/// 0.
///
/// Example:
///
/// ```mbt check
/// test {
/// let src = [1, 2, 3, 4, 5]
/// let dst = [0, 0]
/// src[:3].blit_to(dst, dst_offset=1)
/// @debug.debug_inspect(dst, content="[0, 1, 2, 3]")
/// }
/// ```
///
/// Panics if:
///
/// * `len` is negative
/// * `src_offset` is negative
/// * `dst_offset` is negative
/// * `dst_offset` exceeds the length of destination array
/// * `src_offset + len` exceeds the length of source array
#label_migration(src_offset, fill=false, msg="Use ArrayView::blit_to instead")
#label_migration(len, fill=false, msg="Use ArrayView::blit_to instead")
pub fn[A] Array::blit_to(
self : Array[A],
dst : Array[A],
len~ : Int32 = self.length(),
src_offset~ : Int32 = 0,
dst_offset~ : Int32 = 0,
) -> Unit {
let old_len = dst.length()
guard! len >= 0 &&
dst_offset >= 0 &&
src_offset >= 0 &&
dst_offset <= old_len &&
len <= self.length() - src_offset
let new_len = dst_offset + len
guard! new_len >= 0
if new_len > dst.capacity() {
dst.realloc(new_len)
}
UninitializedArray::unsafe_blit(
dst.buffer(),
dst_offset,
self.buffer(),
src_offset,
len,
)
if new_len > old_len {
dst.len = new_len
}
}
///|
/// Copies all elements from an array view to a destination array, with support
/// for growing the destination array if needed.
///
/// Parameters:
///
/// * `self` : The array view to copy elements from.
/// * `dst` : The array to copy elements to. Will be automatically grown
/// if needed to accommodate the copied elements.
/// * `dst_offset` : Starting index in the destination array. Defaults to 0.
///
/// Example:
///
/// ```mbt check
/// test {
/// let src = [1, 2, 3, 4, 5]
/// let view = src[1:4] // view = [2, 3, 4]
/// let dst = [0, 0]
/// view.blit_to(dst, dst_offset=1)
/// @debug.debug_inspect(dst, content="[0, 2, 3, 4]")
/// }
/// ```
///
/// Panics if:
///
/// * `dst_offset` is negative
/// * `dst_offset` exceeds the length of destination array
pub fn[A] ArrayView::blit_to(
self : ArrayView[A],
dst : Array[A],
dst_offset~ : Int32 = 0,
) -> Unit {
let len = self.length()
let old_len = dst.length()
guard! dst_offset >= 0 && dst_offset <= old_len
let new_len = dst_offset + len
guard! new_len >= 0
if new_len > dst.capacity() {
dst.realloc(new_len)
}
UninitializedArray::unsafe_blit(
dst.buffer(),
dst_offset,
self.buf(),
self.start(),
len,
)
if new_len > old_len {
dst.len = new_len
}
}
///|
/// Removes the last element from an array and returns it, or `None` if it is empty.
///
/// The vacated slot goes on referring to the returned element, so an
/// `ArrayView` created beforehand keeps observing that element, and it is
/// released only once a later push reuses the slot, the buffer grows, or the
/// buffer is dropped. Call `Array::release_unused` to release it at once.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [1, 2, 3]
/// @test.assert_eq(v.pop(), Some(3))
/// @test.assert_eq(v, [1, 2])
/// }
/// ```
pub fn[T] Array::pop(self : Array[T]) -> T? {
let len = self.length()
if len == 0 {
None
} else {
let index = len - 1
let v = self.unsafe_get(index)
// The slot keeps referring to `v` until it is reused or the buffer dies;
// see `unsafe_truncate_to_length` for why it is not nulled out.
self.len = index
Some(v)
}
}
///|
/// Removes and returns the last element from the array.
///
/// Parameters:
///
/// * `array` : The array from which to remove and return the last element.
///
/// Returns the last element of the array before removal.
///
/// As with `Array::pop`, the vacated slot goes on referring to the returned
/// element until a later push reuses it, the buffer grows, the buffer is
/// dropped, or `Array::release_unused` overwrites it.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr = [1, 2, 3]
/// inspect(arr.unsafe_pop(), content="3")
/// @debug.debug_inspect(arr, content="[1, 2]")
/// }
/// ```
///
#internal(unsafe, "Panic if the array is empty.")
#doc(hidden)
#alias(pop_exn, deprecated)
pub fn[T] Array::unsafe_pop(self : Array[T]) -> T {
let len = self.length()
guard! len != 0
let index = len - 1
let v = self.unsafe_get(index)
self.len = index
v
}
///|
/// Removes and returns the element at position index within the array,
/// shifting all elements after it to the left.
///
/// This function will panic if the index is out of bounds.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// @test.assert_eq(v.remove(1), 4)
/// @test.assert_eq(v, [3, 5])
/// }
/// ```
pub fn[T] Array::remove(self : Array[T], index : Int32) -> T {
guard index >= 0 && index < self.length() else {
abort(
"index out of bounds: the len is from 0 to \{self.length()} but the index is \{index}",
)
}
let value = self.unsafe_get(index)
UninitializedArray::unsafe_blit(
self.buffer(),
index,
self.buffer(),
index + 1,
self.length() - index - 1,
)
self.unsafe_truncate_to_length(self.length() - 1)
value
}
///|
/// Removes the specified range from the array and returns it.
///
/// This functions returns an array range from `begin` to `end` `[begin, end)`
///
/// This function will panic if the index is out of bounds.
///
/// # Example
/// ```mbt check
/// test {
/// let v = [3, 4, 5]
/// let vv = v.drain(1, 2) // vv = [4], v = [3, 5]
/// @test.assert_eq(vv, [4])
/// @test.assert_eq(v, [3, 5])
/// }
/// ```
///
/// The `end - begin` slots vacated at the end of the array are not cleared:
/// each keeps whatever it held before the survivors were shifted down, so a
/// drained element or a duplicate reference to a survivor stays reachable
/// there until the slot is reused, the buffer grows, or the array is dropped.
/// Call `Array::release_unused` to overwrite them at once, or
/// `Array::shrink_to_fit` to move the survivors into an exact-size buffer.
///
pub fn[T] Array::drain(self : Array[T], begin : Int32, end : Int32) -> Array[T] {
guard! begin >= 0 && end <= self.length() && begin <= end
let num = end - begin
let v = {
buf: UninitializedArray::make_and_blit(
self.buffer(),
allocate_len=num,
src_offset=begin,
len=num,
),
len: num,
}
UninitializedArray::unsafe_blit(
self.buffer(),
begin,
self.buffer(),
end,
self.length() - end,
)
self.unsafe_truncate_to_length(self.length() - num)
v
}
///|
/// Inserts an element at a given index within the array.
/// This function will panic if the index is out of bounds.
///
/// # Example
/// ```mbt check
/// test {
/// let a = [1, 2, 3]
/// a.insert(1, 4)
/// @debug.debug_inspect(a, content="[1, 4, 2, 3]")
/// let b = [1, 2, 3]
/// b.insert(0, 5)
/// @debug.debug_inspect(b, content="[5, 1, 2, 3]")
/// let c = [1, 2, 3]
/// c.insert(3, 6)
/// @debug.debug_inspect(c, content="[1, 2, 3, 6]")
/// }
/// ```
#owned(value)
pub fn[T] Array::insert(self : Array[T], index : Int32, value : T) -> Unit {
guard index >= 0 && index <= self.length() else {
abort(
"index out of bounds: the len is from 0 to \{self.length()} but the index is \{index}",
)
}
if self.length() == self.buffer().0.length() {
self.realloc(self.length() + 1)
}
UninitializedArray::unsafe_blit(
self.buffer(),
index + 1,
self.buffer(),
index,
self.length() - index,
)
let length = self.length()
self.unsafe_set(index, value)
self.len = length + 1
}
///|
/// Fills an Array with a specified value.
///
/// This method fills all or part of an Array with the given value.
///
/// # Parameters
/// - `value`: The value to fill the array with
/// - `start`: The starting index (inclusive, default: 0)
/// - `end`: The ending index (exclusive, optional)
///
/// If `end` is not provided, fills from `start` to the end of the array.
/// If `start` equals `end`, no elements are modified.
///
/// # Panics
/// - Panics if `start` is negative or greater than or equal to the array length
/// - Panics if `end` is provided and is less than `start` or greater than array length
/// - Does nothing if the array is empty
///
/// # Example
/// ```mbt check
/// test {
/// // Fill entire array
/// let arr = [1, 2, 3, 4, 5]
/// arr.fill(0)
/// @debug.debug_inspect(arr, content="[0, 0, 0, 0, 0]")
///
/// // Fill from index 1 to 3 (exclusive)
/// let arr2 = [1, 2, 3, 4, 5]
/// arr2.fill(99, start=1, end=3)
/// @debug.debug_inspect(arr2, content="[1, 99, 99, 4, 5]")
///
/// // Fill from index 2 to end
/// let arr3 = ["a", "b", "c", "d"]
/// arr3.fill("x", start=2)
/// @debug.debug_inspect(
/// arr3,
/// content=(
/// #|["a", "b", "x", "x"]
/// ),
/// )
/// }
/// ```
#owned(value)
pub fn[A] Array::fill(
self : Array[A],
value : A,
start~ : Int32 = 0,
end? : Int32,
) -> Unit {
let array_length = self.length()
guard array_length > 0 else { return }
guard! start >= 0 && start < array_length
let length = match end {
None => array_length
Some(e) => {
guard! e >= start && e <= array_length
e
}
}
self.buf.unchecked_fill(start, value, length - start)
}
///|
/// Creates and returns a new array with a copy of all elements from the input
/// array.
///
/// Parameters:
///
/// * `array` : The array to be copied.
///
/// Returns a new array containing all elements from the original array.
///
/// Example:
///
/// ```mbt check
/// test {
/// let original = [1, 2, 3]
/// let copied = original.copy()
/// @debug.debug_inspect(copied, content="[1, 2, 3]")
/// inspect(physical_equal(original, copied), content="false")
/// }
/// ```
#alias(clone, deprecated)
pub fn[T] Array::copy(self : Array[T]) -> Array[T] {
let len = self.length()
if len == 0 {
return []
}
let arr = Array::unsafe_make_uninit(len)
Array::unsafe_blit(arr, 0, self, 0, len)
arr
}
///|
/// 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, or its storage reused if exclusively owned on
/// native and linear-memory Wasm.
///
/// 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]")
/// }
/// ```
#cfg(any(target="native", target="wasm"))
#owned(arr)
pub fn[T] Array::from_fixed_array(arr : FixedArray[T]) -> Array[T] {
let len = arr.length()
if unsafe_is_unique_ref(arr) {
{ buf: arr[:].buf(), len, }
} else {
Array::unsafe_make_and_blit_from_fixed(arr, allocate_len=len, len~)
}
}
///|
/// 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, or its storage reused if exclusively owned on
/// native and linear-memory Wasm.
///
/// 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]")
/// }
/// ```
#cfg(not(any(target="native", target="wasm")))
#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 : Int32, elem : T) -> Array[T] {
let arr = Array::unsafe_make_uninit(len)
for i in 0.. i * 2)
/// debug_inspect(arr, content="[0, 2, 4]")
/// }
/// ```
#locals(f)
pub fn[T] Array::makei(
length : Int32,
f : (Int32) -> T raise?,
) -> Array[T] raise? {
if length <= 0 {
[]
} else {
let array = Array::unsafe_make_uninit(length)
for i in 0.. Int32 {
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[Int32] = [1, 2, 3]
/// inspect(arr.unsafe_get(1), content="2")
/// }
/// ```
///
#intrinsic("%array.unsafe_get")
pub fn[T] Array::unsafe_get(self : Array[T], idx : Int32) -> 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 : Array[Int32] = [1, 2, 3]
/// inspect(arr[1], content="2")
/// }
/// ```
///
#intrinsic("%array.get")
#alias("_[_]")
pub fn[T] Array::at(self : Array[T], index : Int32) -> 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")
/// }
/// ```
#intrinsic("%array.get_opt")
pub fn[T] Array::get(self : Array[T], index : Int32) -> 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 : Int32, 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 : Int32, 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 : (Int32, 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 : (Int32, 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.
///
/// Emptying an array is a removal like any other: the buffer keeps referring to
/// the elements that were in it, and they are released once later pushes reuse
/// those slots, the buffer grows, or the array is dropped. Call
/// `Array::release_unused` to overwrite them at once, or `Array::shrink_to_fit`
/// to hand the buffer back entirely. On the JavaScript backend the removed
/// elements are released right away and `Array::release_unused` is a no-op.
///
/// # 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::unsafe_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 : (Int32, T) -> U raise?,
) -> Array[U] raise? {
if self.is_empty() {
return []
}
let arr = Array::unsafe_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 : (Int32, 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? {
[
for v in self if f(v) => v
]
}
///|
/// 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[Int32] = []
/// 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[Int32] = []
/// 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::unsafe_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::unsafe_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[Int32] = []
/// 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) -> Int32 {
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?,
) -> Int32 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.has_prefix([1, 2]), content="true")
/// inspect(arr.has_prefix([2, 3]), content="false")
/// inspect(arr.has_prefix([]), content="true")
/// inspect(arr.has_prefix([1, 2, 3, 4, 5, 6]), content="false")
/// }
/// ```
#alias(starts_with, deprecated="Use `has_prefix` instead")
pub fn[T : Eq] Array::has_prefix(
self : Array[T],
prefix : ArrayView[T],
) -> Bool {
self[:].has_prefix(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.has_suffix([4, 5]), content="true")
/// inspect(arr.has_suffix([3, 4]), content="false")
/// inspect(arr.has_suffix([]), content="true")
/// let arr : Array[Int32] = []
/// inspect(arr.has_suffix([]), content="true")
/// inspect(arr.has_suffix([1]), content="false")
/// }
/// ```
#alias(ends_with, deprecated="Use `has_suffix` instead")
pub fn[T : Eq] Array::has_suffix(
self : Array[T],
suffix : ArrayView[T],
) -> Bool {
self[:].has_suffix(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) -> Int32? {
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?,
) -> Int32? 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[Int32, Int32] {
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) -> Int32 raise?,
) -> Result[Int32, Int32] 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 : Int32, j : Int32) -> 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 : Int32, 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::unsafe_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 : Int32) -> 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::Array(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 : (Int32, 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 : (Int32, 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[Int32] = []
/// 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[Int32] = []
/// debug_inspect(arr.chunks(3), content="[]")
/// }
/// ```
pub fn[T] Array::chunks(self : Array[T], size : Int32) -> Array[ArrayView[T]] {
self[:].chunks(size)
}
///|
/// 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 views, where each view 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[Int32] = []
/// 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 : Int32) -> Array[ArrayView[T]] {
self[:].windows(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~)
}
///|
/// 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[Int32, A] {
self[:].iter2()
}
///|
/// Creates a new empty array.
///
/// Returns an empty array of type `Array[T]`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let arr : Array[Int32] = []
/// 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.
///
/// Elements beyond `len` are removed from the array, but the backing buffer
/// keeps referring to them: they are released once those slots are reused by
/// later pushes, once the buffer grows, or once the array is dropped. Call
/// `Array::release_unused` to overwrite them at once, or `Array::shrink_to_fit`
/// to move the survivors into an exact-size buffer. On the JavaScript backend
/// the removed elements are released right away and `Array::release_unused` is a
/// no-op.
///
/// 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 : Int32) -> 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 = [|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()
/// rng.int(limit=upper)
/// }
///
/// Array::shuffle_in_place(arr, rand~)
/// }
/// ```
#locals(rand)
pub fn[T] Array::shuffle_in_place(
self : Array[T],
rand~ : (Int32) -> Int32,
) -> 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 : Int32) -> Int32 {
/// let rng = @random.Rand()
/// rng.int(limit=upper)
/// }
///
/// let _shuffled = Array::shuffle(arr, rand~)
/// ```
pub fn[T] Array::shuffle(self : Array[T], rand~ : (Int32) -> Int32) -> Array[T] {
let new_arr = self.copy()
Array::shuffle_in_place(new_arr, rand~)
new_arr
}
///|
/// Applies a function to each element of the array and collects the results
/// that are `Some`, discarding the elements for which the function returns
/// `None`.
///
/// # Arguments
///
/// * `self` - The array to filter and map.
/// * `f` - The function applied to each element, returning `Some(value)` to
/// keep the mapped `value`, or `None` to drop the element.
///
/// # Returns
///
/// A new array containing the unwrapped `Some` results, in the order the
/// corresponding elements appeared in the original array.
#locals(f)
pub fn[A, B] Array::filter_map(
self : Array[A],
f : (A) -> B? raise?,
) -> Array[B] raise? {
[
for x in self if f(x) is Some(y) => y
]
}
///|
/// 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[Int32] = []
/// 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(capacity=self.length())
let arr2 : Array[T2] = Array(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_iter2(arr2).to_array(),
/// content="[(1, 'a'), (2, 'b'), (3, 'c')]",
/// )
/// }
/// ```
#alias(zip_to_iter2, deprecated)
pub fn[A, B] Array::zip_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],
) -> Int32 {
self[:].lexical_compare(other)
}