Skip to content

add @array.zip and @array.zip_with #1575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions array/array.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,112 @@ pub fn last[A](self : Array[A]) -> A? {
}
}

///|
/// 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:
///
/// ```moonbit
/// test "zip" {
/// let arr1 = [1, 2, 3]
/// let arr2 = ['a', 'b', 'c']
/// inspect!(arr1.zip(arr2), content="[(1, 'a'), (2, 'b'), (3, 'c')]")
/// }
/// ```
pub fn zip[A, B](self : Array[A], other : Array[B]) -> Array[(A, B)] {
let length = if self.length() < other.length() {
self.length()
} else {
other.length()
}
let arr = Array::new(capacity=length)
for i = 0; i < length; i = i + 1 {
arr.push((self[i], other[i]))
} else {
return arr
}
}

///|
/// Zips two arrays into a single array by applying a function to each pair of elements.
///
/// Parameters:
///
/// * `l` : The first array.
/// * `r` : The second array.
/// * `merge` : A function that takes two arguments, one from each array, and returns a value.
///
/// Returns an array containing the results of applying the function to each pair of elements.
///
/// Example:
///
/// ```moonbit
/// test "zip_with" {
/// let arr1 = [1, 2, 3]
/// let arr2 = [4, 5, 6]
/// let add = fn(a, b) { a + b }
/// inspect!(zip_with(arr1, arr2, add), content="[5, 7, 9]")
/// }
/// ```
pub fn zip_with[A, B, C](
l : Array[A],
r : Array[B],
merge : (A, B) -> C
) -> Array[C] {
let length = if l.length() < r.length() { l.length() } else { r.length() }
let arr = Array::new(capacity=length)
for i = 0; i < length; i = i + 1 {
arr.push(merge(l[i], r[i]))
} else {
return arr
}
}

///|
/// 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:
///
/// ```moonbit
/// test "zip_to_iter2" {
/// let arr1 = [1, 2, 3]
/// let arr2 = ['a', 'b', 'c']
/// inspect!(arr1.zip_to_iter2(arr2).to_array(), content="[(1, 'a'), (2, 'b'), (3, 'c')]")
/// }
/// ```
pub fn zip_to_iter2[A, B](self : Array[A], other : Array[B]) -> Iter2[A, B] {
let length = if self.length() < other.length() {
self.length()
} else {
other.length()
}
Iter2::new(fn(yield_) {
for i = 0; i < length; i = i + 1 {
if yield_(self[i], other[i]) == IterEnd {
break IterEnd
}
} else {
IterContinue
}
})
}

///|
pub impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for Array[X] with arbitrary(
size,
Expand Down
8 changes: 8 additions & 0 deletions array/array.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ fn sort_by[T](Array[T], (T, T) -> Int) -> Unit

fn sort_by_key[T, K : Compare](Array[T], (T) -> K) -> Unit

fn zip[A, B](Array[A], Array[B]) -> Array[(A, B)]

fn zip_to_iter2[A, B](Array[A], Array[B]) -> Iter2[A, B]

fn zip_with[A, B, C](Array[A], Array[B], (A, B) -> C) -> Array[C]

// Types and methods
impl FixedArray {
all[T](Self[T], (T) -> Bool) -> Bool
Expand Down Expand Up @@ -78,6 +84,8 @@ impl Array {
sort[T : Compare](Self[T]) -> Unit
sort_by[T](Self[T], (T, T) -> Int) -> Unit
sort_by_key[T, K : Compare](Self[T], (T) -> K) -> Unit
zip[A, B](Self[A], Self[B]) -> Self[(A, B)]
zip_to_iter2[A, B](Self[A], Self[B]) -> Iter2[A, B]
}
impl[X : @quickcheck.Arbitrary] @quickcheck.Arbitrary for Array[X]

Expand Down
68 changes: 68 additions & 0 deletions array/array_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,74 @@ test "Array::last" {
inspect!([1].last(), content="Some(1)")
}

///|
test "zip" {
// Test with two non-empty arrays
let arr1 = [1, 2, 3]
let arr2 = ['a', 'b', 'c']
inspect!(arr1.zip(arr2), content="[(1, 'a'), (2, 'b'), (3, 'c')]")

// Test with arrays of different lengths
let arr3 = [1, 2]
let arr4 = ["a", "b", "c"]
inspect!(
arr3.zip(arr4),
content=
#|[(1, "a"), (2, "b")]
,
)

// Test with an empty array
let arr5 : Array[Int] = []
let arr6 = ["a", "b", "c"]
inspect!(arr5.zip(arr6), content="[]")
}

///|
test "zip_with" {
// Test with two non-empty arrays and a function
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
let add = fn(a, b) { a + b }
inspect!(zip_with(arr1, arr2, add), content="[5, 7, 9]")

// Test with arrays of different lengths and a function
let arr3 = [1, 2]
let arr4 = [4, 5, 6]
inspect!(zip_with(arr3, arr4, add), content="[5, 7]")

// Test with an empty array and a function
let arr5 : Array[Int] = []
let arr6 = [4, 5, 6]
inspect!(zip_with(arr5, arr6, add), content="[]")
}

///|
test "zip_to_iter2" {
// Test with two non-empty arrays
let arr1 = [1, 2, 3]
let arr2 = ['a', 'b', 'c']
inspect!(
arr1.zip_to_iter2(arr2).to_array(),
content="[(1, 'a'), (2, 'b'), (3, 'c')]",
)

// Test with arrays of different lengths
let arr3 = [1, 2]
let arr4 = ["a", "b", "c"]
inspect!(
arr3.zip_to_iter2(arr4).to_array(),
content=
#|[(1, "a"), (2, "b")]
,
)

// Test with an empty array
let arr5 : Array[Int] = []
let arr6 = ["a", "b", "c"]
inspect!(arr5.zip_to_iter2(arr6), content="[]")
}

///|
test "arbitrary" {
let arr : Array[Array[Int]] = @quickcheck.samples(20)
Expand Down
19 changes: 7 additions & 12 deletions immut/list/list.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -466,25 +466,20 @@ pub fn fold_righti[A, B](self : T[A], f : (Int, A, B) -> B, init~ : B) -> B {

///|
/// Zip two lists.
/// If the lists have different lengths, it will return None.
/// If the lists have different lengths, it will return a list with shorter length.
///
/// # Example
///
/// ```
/// let r = @list.zip(@list.of([1, 2, 3, 4, 5]), @list.of([6, 7, 8, 9, 10]))
/// assert_eq!(r, Some(@list.from_array([(1, 6), (2, 7), (3, 8), (4, 9), (5, 10)])))
/// assert_eq!(r, @list.from_array([(1, 6), (2, 7), (3, 8), (4, 9), (5, 10)]))
/// ```
pub fn zip[A, B](self : T[A], other : T[B]) -> T[(A, B)]? {
let mut acc = Nil
let res = loop self, other {
Nil, Nil => break Some(acc)
Cons(x, xs), Cons(y, ys) => {
acc = Cons((x, y), acc)
continue xs, ys
}
_, _ => break None
pub fn zip[A, B](self : T[A], other : T[B]) -> T[(A, B)] {
match (self, other) {
(Nil, _) => Nil
(_, Nil) => Nil
(Cons(x, xs), Cons(y, ys)) => Cons((x, y), zip(xs, ys))
}
res.map(T::rev)
}

///|
Expand Down
4 changes: 2 additions & 2 deletions immut/list/list.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fn unsafe_nth[A](T[A], Int) -> A

fn unzip[A, B](T[(A, B)]) -> (T[A], T[B])

fn zip[A, B](T[A], T[B]) -> T[(A, B)]?
fn zip[A, B](T[A], T[B]) -> T[(A, B)]

// Types and methods
pub(all) enum T[A] {
Expand Down Expand Up @@ -249,7 +249,7 @@ impl T {
unsafe_minimum[A : Compare](Self[A]) -> A
unsafe_nth[A](Self[A], Int) -> A
unzip[A, B](Self[(A, B)]) -> (Self[A], Self[B])
zip[A, B](Self[A], Self[B]) -> Self[(A, B)]?
zip[A, B](Self[A], Self[B]) -> Self[(A, B)]
}
impl[A] Add for T[A]
impl[A : Compare] Compare for T[A]
Expand Down
9 changes: 6 additions & 3 deletions immut/list/list_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ test "zip" {
let rs = @list.of([6, 7, 8, 9, 10])
inspect!(
ls.zip(rs),
content="Some(@list.of([(1, 6), (2, 7), (3, 8), (4, 9), (5, 10)]))",
content="@list.of([(1, 6), (2, 7), (3, 8), (4, 9), (5, 10)])",
)
}

Expand Down Expand Up @@ -607,13 +607,16 @@ test "List::zip with lists of equal length" {
let list1 = @list.of([1, 2, 3])
let list2 = @list.of(["a", "b", "c"])
let zipped = list1.zip(list2)
let expected = Some(@list.of([(1, "a"), (2, "b"), (3, "c")]))
let expected = @list.of([(1, "a"), (2, "b"), (3, "c")])
assert_eq!(zipped, expected)
}

///|
test "@list.zip with empty list" {
inspect!(@list.of([1]).zip((@list.Nil : @list.T[Int])), content="None")
inspect!(
@list.of([1]).zip((@list.Nil : @list.T[Int])),
content="@list.of([])",
)
}

///|
Expand Down
3 changes: 1 addition & 2 deletions immut/list/moon.pkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"moonbitlang/core/builtin",
"moonbitlang/core/array",
"moonbitlang/core/quickcheck",
"moonbitlang/core/json",
"moonbitlang/core/option"
"moonbitlang/core/json"
],
"test-import": [
"moonbitlang/core/double"
Expand Down