How to iterate through two arrays at once?

Thank you for suggesting that! (sorry I hadn't found it myself).

Now I have it working:

let a = [0, 1, 2, 3, 4];
let mut b = [0; 4];
for it in a.iter().zip(b.iter_mut()) {
    let (ai, bi) = it;
    *bi = 2 * *ai;
}

However, don't you think the notation above would have been more clear?

Thanks

1 Like

Your initial notation would require special language support to recognize if I'm not mistaken. That has to be balanced against clarity and the cost of alternatives. In this case, zip is a pretty standard solution to your problem. (Although perhaps it is more well known to the functional programmers.)

I wonder, if zip should accept IntoIterator instead of Iterator.

What do you mean by "special language support"? Couldn't it be implemented with impl IntoIterator for (A, B) where A: Iterator, B: Iterator?

1 Like

Probably. My mistake. I shouldn't make such claims so early in the morning. :stuck_out_tongue:

2 Likes

It has been discussed and postponed until more thoughts come up in this direction (and probably until variadic generics)

Closed RFC-PR:
https://github.com/rust-lang/rfcs/pull/870

Open RFC-Issue:
https://github.com/rust-lang/rfcs/issues/930

Internals-discussion:

2 Likes

FYI, you can use for (ai, bi) in (and so on) here.

The reddit discussion also.

1 Like

Thank you very much for directing me to those links, I'll add my big :+1::+1: to the thread on internals.rust-lang.org.

In my opinion tuple notation would be very convenient and clear. The specific iteration sequence could be specified through brackets nesting.

For instance:

for (x, y, z) in (1..10, 2..11, 3..13) {...}

would give ((1,2,3), (2,3,4) ... (9,10,12))

for (x, y, z) in (1..10, (2..11, 3..13)) {...}

would give ((1,2,3), (1,3,4) ... (2,2,3), (2,3,4) ... (9,10,12))

for (x, y, z) in (1..10, (2..11, (3..13))) {...}

would give ((1,2,3), (1,2,4) ... (1,3,3), (1,3,4) ... (9,10,12))

This notation in my opinion would be much clearer and compact than using .zip().

I'll add here another point which is still related to iterating through two arrays. Things get more complex with multidimensional arrays. In this case instead of writing:

for row in m1.iter_mut() { for el in row.iter_mut() {...} }

it would be convenient to be able to write:

for el in m1.iter_mut().iter_mut() {...}

I'll clarify this point with the following example. I have two 2D arrays, and I need to cycle through each element of them, so I could write for instance:

for i in 0..s {
    for j in 0..s {
        m2[i][j] = m1[i][j].powi(2);
    }
}

To avoid bound checking, I would prefer to use .iter(), but this is what I need to write with the current Rust notation:

for (m1_row, m2_row) in m1.iter().zip(m2.iter_mut()) {
    for (m1_el, m2_el) in m1_row.iter().zip(m2_row.iter_mut()) {
        *m2_el = m1_el.powi(2);
    }
}

This looks quite complex, it would be nice to be able to write:

for (m1_el, m2_el) in (m1.iter().iter(), m2.iter_mut().iter_mut()) {
    *m2_el = m1_el.powi(2);
}

Here is another example:

for i in 0..s {
    for j in 0..s {
        if ((m2[i][j]-m1[i][j])/m1[i][j]).abs() > 1E-6 { test = false; }
    }
}

In this case I could not figure out how to use .iter() at all... (I tried different possibilities but I always get compilation errors one way or another).

Thanks

Edit: now the following code works, I guess something was corrected in the last nightly releases:

for (m1_row, m2_row) in m1.iter().zip(m2.iter()) {
    for (m1_el, m2_el) in m1_row.iter().zip(m2_row.iter()) {
        if ((m2_el-m1_el)/m1_el).abs() > 1E-6 { test = false; }
    }
}

1 Like

I'm not sure if that would be a very good general solution because you might want to iterate row first or column first. Theoretically, you could do something like this:

// joint_index would return an tuple of iterators which is based
// on the order of numbers. This case would be equivalent to
// `array1[k,i,j]`. `joint_index(0,1,2)` would be `array1[i,j,k]`.
for (a1, a2, a3) in (array1, array2, array3).joint_index(2, 0, 1) {
    if ((a2-a1)/a1).abs() > 1E-6 { test = false; }
}

Having said that, I don't know how to make your example work.

To specify the iteration order, an optional integer parameter could be added to .iter(), with default 0 indicating iteration over the first dimension available.

To iterate over two dimensions in order (first-dimension, then second-dimension), one could write either:

for el in a2D.iter(0).iter(0) {...}

or:

for el in a2D.iter().iter() {...}

while to iterate in reverse order (second-dimension, then first-dimension), one could write either:

for el in a2D.iter(1).iter(0) {...}

or:

for el in a2D.iter(1).iter() {...}

Considering a 3D array, to iterate in order over second-dimension, first-dimension, third-dimension, one could write either:

for el in a3D.iter(2).iter(0).iter(0) {...}

or:

for el in a3D.iter(2).iter().iter() {...}

I jus tried to use this but am told that iter does not take a parameter.

What @lucatrv was typing was just an idea.

To iterate several iterators at a time we use .zip():

for (a, b) in array1.iter().zip(array2.iter()) {
}
1 Like

Hi, For processing verticies in 3D or audio buffers, the following process is commonly used in C

void add(float* arr1, float* arr2, float* res) {
   while(arr1){
    (*res)++ = (*arr1)++ + (*arr2)++; (ugly to read but reveals what is really being performed)
   }
} 

which mean we iterate as long as the pointer points to something.

I'm wondering what is the best methode in RUST to process arrays of simple data the most efficient way

Hi, zip is still the way to go. Your example can be translated to:

fn add(arr1: &[f32], arr2: &[f32], res: &mut [f32]) {
    for (res, (arr1, arr2)) in res.iter_mut().zip(arr1.iter().zip(arr2.iter())) {
        *res = *arr1 + *arr2;
    }
}

zip will prevent out-of-bounds access if arr2 or res is shorter than arr1 as opposed to your example.
LLVM should remove any bound check and vectorize the loop but if it's really important I'd check to see if it's the case.

thanks, Usually those pieces of algo assumes that the user is dealing with buffers of the same size.

It seems that this RUST sample is doing much more that it's C equivalent, I was worrying about performance

At this point isn't doing this a better solution in terms of readability and performance

fn mult_arrays(arr1: &[i32], arr2: &[i32], res: &mut [i32]) {
//supposing I'm sure that all arrays has the same size
    for i in 0..arr1.len() {
         res[i] = arr1[i] + arr2[i];
    }
}

LLVM usually optimizes iterators better than (or the same as) loop + indexing. There is a chapter in the book about it.

1 Like

You can use itertools::izip - Rust which delagates to .zip internally and expands the inner tuple.

use itertools::izip;

fn add(arr1: &[f32], arr2: &[f32], res: &mut [f32]) {
    for (res, arr1, arr2) in izip!(res.iter_mut(), arr1, arr2) {
        *res = *arr1 + *arr2;
    }
}
3 Likes