/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
package std.core
@ConstSafe
public struct Array<T> {
let rawptr: RawArray<T>
let start: Int64
let len: Int64
@Frozen
func toRawArray() {
if (len == 0) {
return RawArray<T>()
}
let newRawPtr = RawArray<T>(len, repeat: unsafe { zeroValue<T>() })
intrinsicBuiltInCopyTo<T>(this.rawptr, newRawPtr, this.start, 0, len)
return newRawPtr
}
@Frozen
public const init() {
this.start = 0
this.rawptr = RawArray<T>()
this.len = 0
}
@Frozen
public init(size: Int64, repeat!: T) {
this.start = 0
this.rawptr = RawArray<T>(size, repeat: repeat)
this.len = size
}
@Frozen
init(elements: Collection<T>) {
this.start = 0
this.rawptr = RawArray<T>(elements)
this.len = arraySize(rawptr)
}
@Frozen
public init(size: Int64, initElement: (Int64) -> T) {
this.start = 0
this.rawptr = RawArray<T>(size, initElement)
this.len = size
}
@Frozen
init(data: RawArray<T>, start: Int64, len: Int64) {
this.rawptr = data
this.start = start
this.len = len
}
@Frozen
public prop first: Option<T> {
get() {
if (this.len == 0) {
return None
}
return this[0]
}
}
@Frozen
public prop last: Option<T> {
get() {
if (this.len == 0) {
return None
}
return this[len - 1]
}
}
/**
* @return slice of this Array
* @throws IndexOutOfBoundsException if `start` is negative, or `len` is negative,
* or `start + len` is greater than the size of array.
*/
@Frozen
public func slice(start: Int64, len: Int64): Array<T> {
if (start < 0 || len < 0 || (start + len) > this.len) {
throw IndexOutOfBoundsException(arraySliceOutOfBoundsExceptionMessage(this.len, start, len))
}
return Array(this.rawptr, this.start + start, len)
}
@OverflowWrapping
@Frozen
public func get(index: Int64): Option<T> {
if (UInt64(index) >= UInt64(this.len)) {
return None<T>
}
return Some<T>(arrayGetUnchecked(this.rawptr, start + index))
}
/**
* @throws IndexOutOfBoundsException if `index` is negative or
* greater than or equal to the size of array.
*/
@Frozen
@OverflowWrapping
public operator func [](index: Int64): T {
// Using type conversion to replace typical bounds checking,
// if (index < 0 || index >= this.len), to a single comparison.
// The negative value of index will be converted to a large positive number
// when `index` and `this.len` are interpreted as UInt64 and will trigger the exception.
if (UInt64(index) >= UInt64(this.len)) {
throw IndexOutOfBoundsException(arrayIndexOutOfBoundsExceptionMessage(this.len, index))
}
return arrayGetUnchecked(this.rawptr, start + index)
}
/**
* @throws IndexOutOfBoundsException if `index` is negative or
* greater than or equal to the size of array.
*/
@Frozen
@OverflowWrapping
public operator func [](index: Int64, value!: T): Unit {
if (UInt64(index) >= UInt64(this.len)) {
throw IndexOutOfBoundsException(arrayIndexOutOfBoundsExceptionMessage(this.len, index))
}
return arraySetUnchecked(this.rawptr, start + index, value)
}
/**
* @throws IllegalArgumentException if the `step` of `range` is not equal to 1.
* @throws IndexOutOfBoundsException if the `range` is invalid for this array.
*/
@Frozen
public operator func [](range: Range<Int64>): Array<T> {
return rangeSlice(range, this.len)
}
@Frozen
public func fill(value: T): Unit {
for (i in 0..this.len) {
arraySetUnchecked(this.rawptr, this.start + i, value)
}
}
/**
* @throws IllegalArgumentException if the `step` of `range` is not equal to 1,
* or the size of `value` is not equal to the length of `range`.
* @throws IndexOutOfBoundsException if `range` is invalid for this array.
*/
@Frozen
public operator func [](range: Range<Int64>, value!: Array<T>): Unit {
if (range.step != 1) {
throw IllegalArgumentException(arrayInvalidRangeStepExceptionMessage(range.step))
}
let (start_, end_): (Int64, Int64) = normalize(range)
if (!isLegal(start_, end_)) {
throw IndexOutOfBoundsException(arrayRangeOutOfBoundsExceptionMessage(
this.len, range.start, range.end, range.step, range.hasStart, range.hasEnd, range.isClosed))
}
let rangeSize = end_ - start_
if (rangeSize != value.size) {
throw IllegalArgumentException(arrayInvalidRangeSizeExceptionMessage(value.size, rangeSize))
}
intrinsicBuiltInCopyTo<T>(value.rawptr, this.rawptr, value.start, this.start + start_, rangeSize)
}
/** Reverse array elements in place. */
@Frozen
@OverflowWrapping
public func reverse(): Unit {
var i: Int64 = start
var j: Int64 = start + len - 1
while (i < j) {
let tmp: T = arrayGet(rawptr, i)
arraySet(rawptr, i, arrayGet(rawptr, j))
arraySet(rawptr, j, tmp)
i = i + 1
j = j - 1
}
}
/**
* Special processing is performed for 0 in the following cases:
* When Range is 0..(excluding 0..=)(that is, start=0, hasStart=true, hasStop=false, isClosed=false)
* If the array or buffer is empty, an empty array or buffer is returned.
* When Range is 0..0(excluding 0..=0)(that is, start=0, hasStart=true, stop=0, hasStop=true, isClosed=false)
* If the array or buffer is empty, an empty array or buffer is returned.
* For other ranges, start and end out-of-bounds check is performed normally,
* including the range that is actually empty.
*
* @param range
* @param size Size of the collection
*
* @return Parameters of start*stop*step*offset.
* @since 0.25.2
*
* @throws IllegalArgumentException if the `step` of `range` is not equal to 1.
* @throws IndexOutOfBoundsException if the `range` is invalid for this array.
*/
@Frozen
private func rangeSlice(range: Range<Int64>, _: Int64): Array<T> {
if (range.step != 1) {
throw IllegalArgumentException(arrayInvalidRangeStepExceptionMessage(range.step))
}
let (start_, end_): (Int64, Int64) = normalize(range)
if (!isLegal(start_, end_)) {
throw IndexOutOfBoundsException(arrayRangeOutOfBoundsExceptionMessage(
this.len, range.start, range.end, range.step, range.hasStart, range.hasEnd, range.isClosed))
}
return Array<T>(this.rawptr, this.start + start_, end_ - start_)
}
// The parameters represents a half-open range [start_, end_)
@Frozen
private const func isLegal(start_: Int64, end_: Int64): Bool {
0 <= start_ && start_ <= end_ && end_ <= this.len
}
// normalize Range<Int64> to half-open range [start, end)
@Frozen
@OverflowWrapping
private const func normalize(range: Range<Int64>): (Int64, Int64) {
var start = if (range.hasStart) {
range.start
} else {
0
}
var end = if (range.hasEnd) {
range.end
} else {
this.len
}
if (range.isClosed && range.hasEnd) {
end += if (range.step > 0) {
1
} else {
-1
}
}
return (start, end)
}
// keep for compatibility - do not remove
private const func rangeToString(range: Range<Int64>): String {
let start = if (range.hasStart) {
range.start
} else {
0
}
let end = if (range.hasEnd) {
range.end
} else {
this.len
}
match ((range.step > 0, range.isClosed)) {
case (true, true) => "[${start}, ${end}]"
case (true, false) => "[${start}, ${end})"
case (false, true) => "[${end}, ${start}]"
case (false, false) => "(${end}, ${start}]"
}
}
/** Dummy function, used for function overloading check. */
@Frozen
public func clone(): Array<T> {
if (len == 0) {
return Array<T>()
}
let newRawPtr = RawArray<T>(len, repeat: unsafe { zeroValue<T>() })
intrinsicBuiltInCopyTo<T>(this.rawptr, newRawPtr, this.start, 0, len)
return Array<T>(newRawPtr, 0, len)
}
/*
* @throws IndexOutOfBoundsException if the `range` is invalid for this array.
*/
@Frozen
@OverflowWrapping
public func clone(range: Range<Int64>): Array<T> {
let (start_, end_): (Int64, Int64) = normalize(range)
let step = range.step
if ((step > 0 && !isLegal(start_, end_)) || (step < 0 && !isLegal(end_ + 1, start_ + 1))) {
throw IndexOutOfBoundsException(arrayRangeOutOfBoundsExceptionMessage(
this.len, range.start, range.end, range.step, range.hasStart, range.hasEnd, range.isClosed))
}
let arr: Array<T> = this
let offset: Int64 = if (step > 0) {
-1
} else {
1
}
let newSize: Int64 = (end_ - start_ + step + offset) / step
if (step == 1 && newSize != 0) {
var newArr: Array<T> = Array<T>(newSize, repeat: unsafe { zeroValue<T>() })
intrinsicBuiltInCopyTo<T>(this.rawptr, newArr.rawptr, this.start + start_, 0, newSize)
return newArr
}
return Array<T>(newSize, {i: Int64 => arr[start_ + step * i]})
}
/*
* @throws IllegalArgumentException if `copyLen` is negative.
* @throws IndexOutOfBoundsException if `srcStart` or `dstStart` is negative, or `srcStart` is
* greater or equal to the size of this array, or `dstStart` is greater or equal to
* the size of `dst`, or `copyLen` is out of bounds.
*/
@Frozen
public func copyTo(dst: Array<T>, srcStart: Int64, dstStart: Int64, copyLen: Int64): Unit {
if (copyLen == 0) {
return
}
if (copyLen < 0) {
throw IllegalArgumentException("Negative copy length.")
}
if (srcStart < 0 || dstStart < 0) {
throw IndexOutOfBoundsException("Array negative index access.")
}
if (srcStart >= this.len) {
throw IndexOutOfBoundsException("SrcStart is greater than or equal to the size of this array.")
}
if (dstStart >= dst.size) {
throw IndexOutOfBoundsException("DstStart is greater than or equal to the size of the target array.")
}
if (copyLen > this.len - srcStart || copyLen > dst.size - dstStart) {
throw IndexOutOfBoundsException("Copy length out of bounds.")
}
intrinsicBuiltInCopyTo<T>(this.rawptr, dst.rawptr, this.start + srcStart, dst.start + dstStart, copyLen)
}
@Frozen
public func copyTo(dst: Array<T>): Unit {
if (this.len == 0) {
return
}
if (dst.size < this.len) {
throw IllegalArgumentException("Dst size is less than the size of this array.")
}
intrinsicBuiltInCopyTo<T>(this.rawptr, dst.rawptr, this.start, dst.start, this.len)
}
/*
* Returns a new Array concat this and that
*
* `other` will be copied to the new array
*
* returns a new array, size is `this.size + that.size`
*/
@Frozen
public func concat(other: Array<T>): Array<T> {
if (this.len == 0) {
return other.clone()
}
if (other.len == 0) {
return this.clone()
}
let newSize = this.len + other.len
let newArr = Array<T>(newSize, repeat: unsafe { zeroValue<T>() })
intrinsicBuiltInCopyTo<T>(this.rawptr, newArr.rawptr, this.start, 0, this.len)
intrinsicBuiltInCopyTo<T>(other.rawptr, newArr.rawptr, other.start, this.len, other.len)
newArr
}
@Frozen
@OverflowWrapping
protected unsafe func getUnchecked(index: Int64): T {
return arrayGetUnchecked(this.rawptr, start + index)
}
@Frozen
@OverflowWrapping
protected unsafe func setUnchecked(index: Int64, element: T): Unit {
return arraySetUnchecked(this.rawptr, start + index, element)
}
@Frozen
public func swap(index1: Int64, index2: Int64): Unit {
if (index1 < 0 || len <= index1) {
throw IllegalArgumentException("Index1 is less than 0 or not less than the size of this array.")
}
if (index2 < 0 || len <= index2) {
throw IllegalArgumentException("Index2 is less than 0 or not less than the size of this array.")
}
if (index1 == index2) {
return
}
let tmp = arrayGetUnchecked(this.rawptr, start + index1)
arraySetUnchecked(this.rawptr, start + index1, arrayGetUnchecked(this.rawptr, start + index2))
arraySetUnchecked(this.rawptr, start + index2, tmp)
}
@Frozen
public func splitAt(mid: Int64): (Array<T>, Array<T>) {
if (mid < 0 || this.len < mid) {
throw IllegalArgumentException("Mid is less than 0 or not less than the size of this array.")
}
if (this.len == 0) {
return (this, this)
}
let left = Array<T>(this.rawptr, this.start, mid)
let right = Array<T>(this.rawptr, this.start + mid, this.len - mid)
return (left, right)
}
@Frozen
public func repeat(n: Int64): Array<T> {
if (n <= 0) {
throw IllegalArgumentException("N is less than or equal to 0.")
}
if (this.len == 0) {
return this
}
let newLen = this.len * n
let newRawPtr = RawArray<T>(newLen, repeat: unsafe { zeroValue<T>() })
for (i in 0..n) {
intrinsicBuiltInCopyTo<T>(this.rawptr, newRawPtr, this.start, this.len * i, this.len)
}
return Array<T>(newRawPtr, 0, newLen)
}
@Frozen
public func map<R>(transform: (T) -> R): Array<R> {
return Array<R>(this.len, {idx => transform(this[idx])})
}
/**
* @description Extracts elements from the array at specified intervals and returns a new array.
*
* @param count the interval for selection.
* @returns a new array containing all elements extracted from the source array at intervals.
* @throws IllegalArgumentException, if count < 0.
*/
@When[env != "ohos"]
public func step(count: Int64): Array<T> {
if (count <= 0) {
throw IllegalArgumentException("Step count must be greater than 0!")
}
if (len > 0) {
let newSize: Int64 = (len + count - 1) / count
var newRawPtr = RawArray<T>(newSize, repeat: unsafe { zeroValue<T>() })
let oldLastIndex: Int64 = (newSize - 1) * count
var j: Int64 = newSize - 1
for (i in oldLastIndex..=0 : -count) {
let value = unsafe { getUnchecked(i) }
arraySet(newRawPtr, j, value)
j--
}
return Array<T>(newRawPtr, 0, newSize)
}
return Array<T>()
}
/**
* @description Extracts a specific number of elements from the array and returns a new array.
*
* @param count the number of elements to take.
* @returns a new array with the specified number of elements taken.
* @throws IllegalArgumentException, if count < 0.
*/
@When[env != "ohos"]
public func take(count: Int64): Array<T> {
if (count == 0) {
return Array<T>()
}
if (count < 0) {
throw IllegalArgumentException("take count must be greater than 0!")
}
let newLen = min(count, this.len)
let newRawPtr = RawArray<T>(newLen, repeat: unsafe { zeroValue<T>() })
intrinsicBuiltInCopyTo<T>(this.rawptr, newRawPtr, this.start, 0, newLen)
return Array<T>(newRawPtr, 0, newLen)
}
/**
* @description Skips a specific number of elements and returns a new array.
*
* @param count the number of elements to skip.
* @returns a new array with the specified number of elements skipped.
* @throws IllegalArgumentException, if count < 0.
*/
@When[env != "ohos"]
public func skip(count: Int64): Array<T> {
if (count < 0) {
throw IllegalArgumentException("skip count must be greater than 0!")
}
if (count == 0) {
return Array(this.toRawArray(), this.start, this.len)
}
let newLen = this.len - count
if (newLen <= 0) {
return Array<T>()
}
let newRawPtr = RawArray<T>(newLen, repeat: unsafe { zeroValue<T>() })
intrinsicBuiltInCopyTo<T>(this.rawptr, newRawPtr, this.start + count, 0, newLen)
return Array<T>(newRawPtr, 0, newLen)
}
/**
* @description Returns a new array containing elements that satisfy the filtering condition.
*
* @param predicate the given condition.
* @returns a new array containing elements that satisfy the filtering condition.
*/
@When[env != "ohos"]
public func filter(predicate: (T) -> Bool): Array<T> {
if (this.len == 0) {
return Array<T>()
}
var newRawPtr = RawArray<T>(this.len, repeat: unsafe { zeroValue<T>() })
var t = 0
for (i in 0..this.len) {
let value = unsafe { getUnchecked(i) }
if (predicate(value)) {
arraySet(newRawPtr, t, value)
t++
}
}
return Array<T>(newRawPtr, 0, t)
}
/**
* @description Applies a transformation closure (transform) to each element in the array, where the closure returns an array, then "flattens" and concatenates all returned arrays into a single result array.
*
* @param transform the given mapping function.
* @returns the new array after being "mapped" and "flattened".
*/
@When[env != "ohos"]
public func flatMap<R>(transform: (T) -> Array<R>): Array<R> {
var result: Array<R> = Array<R>()
for (i in 0..this.len) {
result = result.concat(transform(unsafe { getUnchecked(i) }))
}
return result
}
/**
* @description Performs filtering and mapping operations simultaneously, returning a new array.
*
* @param transform the given mapping function. If the function's return value is Some, it corresponds to the filter's predicate being true;
otherwise, it is false.
* @returns a new array after filtering and mapping.
*/
@When[env != "ohos"]
public func filterMap<R>(transform: (T) -> ?R): Array<R> {
if (this.len == 0) {
return Array<R>()
}
let newRawPtr = RawArray<R>(this.len, repeat: unsafe { zeroValue<R>() })
var j = 0
for (i in 0..this.len) {
let value = unsafe { getUnchecked(i) }
match (transform(value)) {
case Some(v) =>
arraySet(newRawPtr, j, v)
j++
case None => continue
}
}
return Array<R>(newRawPtr, 0, j)
}
/**
* @description Returns a new array with the given element inserted between every two elements.
*
* @param separator the given element.
* @returns a new array.
*/
@When[env != "ohos"]
public func intersperse(separator: T): Array<T> {
if (this.len < 2) {
return this.clone()
}
let newSize: Int64 = 2 * this.len - 1
let newRawPtr = RawArray<T>(newSize, repeat: unsafe { zeroValue<T>() })
for (i in 0..this.len - 1) {
arraySet(newRawPtr, 2*i, unsafe { getUnchecked(i) })
arraySet(newRawPtr, 2*i + 1, separator)
}
arraySet(newRawPtr, newSize - 1, unsafe { getUnchecked(this.len - 1) })
return Array<T>(newRawPtr,0,newSize)
}
/**
* @description Iterates over all elements and performs the given operation.
*
* @param action the given operation function.
*/
@When[env != "ohos"]
public func forEach(action: (T) -> Unit): Unit {
for (i in 0..this.len) {
action(unsafe{ getUnchecked(i) })
}
}
/**
* @description Determine whether all elements in the array satisfy the condition.
*
* @param predicate the given condition.
* @returns true if all elements in the array satisfy the condition, otherwise returns false.
*/
@When[env != "ohos"]
public func all(predicate: (T) -> Bool): Bool {
for (i in 0..this.len) {
if (!predicate(unsafe { getUnchecked(i) })) {
return false
}
}
return true
}
/**
* @description Determine whether all elements in the array satisfy the condition.
*
* @param predicate the given condition.
* @returns Whether there is any element that satisfies the condition.
*/
@When[env != "ohos"]
public func any(predicate: (T) -> Bool): Bool {
for (i in 0..this.len) {
if (predicate(unsafe{getUnchecked(i)})) {
return true
}
}
return false
}
/**
* @description Determine whether all elements in the array do not satisfy the condition.
*
* @param separator the given element.
* @returns a new array.
*/
@When[env != "ohos"]
public func none(predicate: (T) -> Bool): Bool {
for (i in 0..this.len) {
if (predicate(unsafe{getUnchecked(i)})) {
return false
}
}
return true
}
/**
* @description Computes from left to right using the specified initial value.
*
* @param initial the given initial value of type R.
* @param operation the given computation function.
* @returns the final computed value.
*/
@When[env != "ohos"]
public func fold<R>(initial: R, operation: (R, T) -> R): R {
var result: R = initial
for (i in 0..this.len) {
result = operation(result, unsafe{getUnchecked(i)})
}
return result
}
/**
* @description Compute from left to right using the first element as the initial value.
*
* @param operation the given computation function.
* @returns the computation result.
*/
@When[env != "ohos"]
public func reduce(operation: (T, T) -> T): Option<T> {
if (this.len == 0) {
return None<T>
}
var result: T = unsafe{getUnchecked(0)}
for (i in 1..this.len) {
result = operation(result, unsafe{getUnchecked(i)})
}
return result
}
}
extend<T> Array<T> {
/**
* @description Merges two arrays into a new array (the length depends on the shorter array).
*
* @param other one of the arrays to be merged.
* @returns a new array.
*/
@When[env != "ohos"]
public func zip<R>(other: Array<R>): Array<(T, R)> {
let newSize: Int64 = min(this.len, other.size)
if (newSize == 0) {
return Array<(T, R)>()
}
let newRawPtr = RawArray<(T,R)>(newSize, repeat: unsafe { zeroValue<(T,R)>() })
for (i in newSize - 1..=0 : -1) {
arraySet(newRawPtr,i,unsafe{(getUnchecked(i),other.getUnchecked(i))})
}
return Array<(T,R)>(newRawPtr,0,newSize)
}
/**
* @description Used to obtain an array with indices.
*
* @returns a new array with indices.
*/
@When[env != "ohos"]
public func enumerate(): Array<(Int64, T)> {
if (this.len == 0) {
return Array<(Int64, T)>()
}
let newRawPtr = RawArray<(Int64,T)>(this.len, repeat: unsafe { zeroValue<(Int64,T)>() })
for (i in this.len - 1..=0 : -1) {
arraySet(newRawPtr,i,(i,unsafe{getUnchecked(i)}))
}
return Array<(Int64,T)>(newRawPtr,0,this.len)
}
}
extend<T> Array<Array<T>> {
@Frozen
public func flatten(): Array<T> {
var newLen = 0
for (i in 0..this.len) {
newLen += this[i].len
}
let newRawPtr = RawArray<T>(newLen, repeat: unsafe { zeroValue<T>() })
var newIdx = 0
for (i in 0..this.len) {
intrinsicBuiltInCopyTo<T>(this[i].rawptr, newRawPtr, this[i].start, newIdx, this[i].len)
newIdx += this[i].len
}
return Array<T>(newRawPtr, 0, newLen)
}
}
private func rangeToString(arraySize: Int64, start: Int64, end: Int64,
step: Int64, hasStart: Bool, hasEnd: Bool, isClosed: Bool): String {
let rangeStart = if (hasStart) {
start
} else {
0
}
let rangeEnd = if (hasEnd) {
end
} else {
arraySize
}
match ((step > 0, isClosed)) {
case (true, true) => "[${rangeStart}, ${rangeEnd}]"
case (true, false) => "[${rangeStart}, ${rangeEnd})"
case (false, true) => "[${rangeEnd}, ${rangeStart}]"
case (false, false) => "(${rangeEnd}, ${rangeStart}]"
}
}
private func arraySliceOutOfBoundsExceptionMessage(arraySize: Int64, sliceStart: Int64, sliceLength: Int64): String {
"The size of Array is ${arraySize}, start is ${sliceStart}, length is ${sliceLength}."
}
private func arrayIndexOutOfBoundsExceptionMessage(arraySize: Int64, index: Int64): String {
"The length of the array is ${arraySize}, but the index is ${index}."
}
private func arrayRangeOutOfBoundsExceptionMessage(
arraySize: Int64, start: Int64, end: Int64, step: Int64, hasStart: Bool, hasEnd: Bool, isClosed: Bool): String {
"The length of the array is ${arraySize}, " +
"but the actual range is ${rangeToString(arraySize, start, end, step, hasStart, hasEnd, isClosed)}."
}
private func arrayInvalidRangeSizeExceptionMessage(arraySize: Int64, rangeSize: Int64): String {
"The length of the value array is ${arraySize}, which does not match the range length of ${rangeSize}."
}
private func arrayInvalidRangeStepExceptionMessage(rangeStep: Int64): String {
"Illegal step ${rangeStep}, step should be 1."
}