56

Is there a more direct and readable way to accomplish the following:

fn main() {
    let a = [1, 2, 3];
    let b = [4, 5, 6];
    let c = [7, 8, 9];
    let iter = a.iter()
        .zip(b.iter())
        .zip(c.iter())
        .map(|((x, y), z)| (x, y, z));
}

That is, how can I build an iterator from n iterables which yields n-tuples?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
anderspitman
  • 9,230
  • 10
  • 40
  • 61

4 Answers4

78

You can use the izip!() macro from the crate itertools, which implements this for arbitrary many iterators:

use itertools::izip;

fn main() {

    let a = [1, 2, 3];
    let b = [4, 5, 6];
    let c = [7, 8, 9];

    // izip!() accepts iterators and/or values with IntoIterator.
    for (x, y, z) in izip!(&a, &b, &c) {

    }
}

You would have to add a dependency on itertools in Cargo.toml, use whatever version is the latest. Example:

[dependencies]
itertools = "0.8"
bluss
  • 12,472
  • 1
  • 49
  • 48
10

You could also create a macro using the .zip provided like,

$ cat z.rs
macro_rules! zip {
    ($x: expr) => ($x);
    ($x: expr, $($y: expr), +) => (
        $x.iter().zip(
            zip!($($y), +))
    )
}


fn main() {
    let x = vec![1,2,3];
    let y = vec![4,5,6];
    let z = vec![7,8,9];

    let zipped = zip!(x, y, z);
    println!("{:?}", zipped);
    for (a, (b, c)) in zipped {
        println!("{} {} {}", a, b, c);
    }
}

Output:

$ rustc z.rs && ./z
Zip { a: Iter([1, 2, 3]), b: Zip { a: Iter([4, 5, 6, 67]), b: IntoIter([7, 8, 9]), index: 0, len: 0 }, index: 0, len: 0 }
1 4 7
2 5 8
3 6 9
han solo
  • 6,390
  • 1
  • 15
  • 19
1

I wanted to be able to do this to an arbitrarily long vector, so I had to implement this by hand:

fn transpose_records<T: Clone>(records: &Vec<Vec<T>>) -> Vec<Vec<T>> {
    let mut transposed: Vec<Vec<T>> = vec![Vec::new(); records[0].len()];

    for record in records {
        for (index, element) in record.iter().enumerate() {
            transposed[index].push(element.clone());
        }
    }

    transposed
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
GChamon
  • 456
  • 5
  • 12
  • 1
    This doesn't [zip](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.zip) an iterator, so it doesn't seem like an answer to this question. – Shepmaster Jun 16 '20 at 17:10
  • [Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?](https://stackoverflow.com/q/40006219/155423) – Shepmaster Jun 16 '20 at 17:10
  • Rust doesn't support variable arguments, Vec here is just serving as a package. But it is mapping N vectors of size M to M vectors of size N, in which the first element of each comes from the first vector, the second from the second and so on. How could I generalize this to all classes of iterators in rust, instead of using vectors? Also, thanks for taking the time to refer to that reference, I learned something new – GChamon Jun 16 '20 at 21:21
  • If I could easily build generators it would be better to construct the function, but `yield` is still experimental. – GChamon Jun 16 '20 at 21:43
  • 1
    @Shepmaster it is an answer to how to translate this Python code to rust: `list(zip(*[[1,2,3],[4,5,6],[7,8,9]]))`. So while it doesn't actually use zip, it does what people like me expect zip to do. It therefore is a useful answer to some interpretations of the question. – BlackShift Dec 16 '20 at 20:33
  • 1
    @BlackShift I have to disagree. It's only equivalent to your Python example for a limited set of possible input values (for instance it won't work for mixed types, like `list(zip(*[[1,2,3],["a","b","c"],[2.5, 3.7, 7.6]]))`). So, while it would be a perfectly fine answer for the question "how can I transpose an n-vector of m-vectors?" it's not an answer to the current question. Also, the question was about iterators without storing the result in a container. Speaking in Python terms again, for the equivalent of `zip(*[[1,2,3],["a","b","c"],[2.5, 3.7, 7.6]])` without generating a list from it. – soulsource May 01 '21 at 12:54
-3

You can now do below, without any external dependencies:

for (inp, want) in inputs.iter().zip(wants.iter()) {
   // ...
}

This approach also supports different types.

Piotr Ostrowski
  • 520
  • 5
  • 8
  • 3
    This does not answer the question. The question is about more than two iterators, and this provides an answer for two iterators. – zeh Dec 27 '22 at 05:04