Iterate over pairs of chunks without creating a temporary vector

6.9k Views Asked by At

I'm trying to iterate a vector as pairs of chunks (in my case it's an image represented as a contiguous bitmap and I'd like to have access to pixels from two rows at once).

The problem is that I can't do .chunks(w).chunks(2), but have to create a temporary vector in between.

Is there a way to do it purely with iterators? (I'm OK if the result is an iterator itself)

playground

let input: Vec<_> = (0..12).collect();

let tmp: Vec<_> = input.chunks(3).collect();
let result: Vec<_> = tmp.chunks(2).collect();

println!("{:?}", result);

[[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]]]

4

There are 4 best solutions below

0
On BEST ANSWER

Oh, I've got it! I can split larger chunks:

input
   .chunks(2 * 100)
   .map(|pair| pair.split_at(100))
0
On

Oh, I've got it! I can split larger chunks:

input.chunks(2*3).map(|dbl| dbl.split_at(3)).collect();

Yes, or you could do this:

let tmp: Vec<_> = input
    .chunks(2 * 3)
    .map(|x| x.chunks(3).collect::<Vec<_>>())
    .collect();

This outputs exactly the same thing as your example, without the mix of tuples and arrays from your solution:

[[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]]]
1
On

Indeed chunk(a).chunk(b) is not possible because chunk() is only available on a slice, whereas the result of chunk() (a Chunk) is not a slice. That's too bad, I don't know what prevents chunk to be implemented on a regular Iterator. (Maybe a lifetime issue?)

A more verbose solution, but still iterator-oriented (i.e., not falling back to an ugly C++-like collecting loop) would be to use the itertools crate, more specifically the method batching(). This is the example from the docs, it does almost the same thing as your chunk(2) except it returns a tuple instead of a slice:

extern crate itertools;

use itertools::Itertools;

fn main() {
    // An adaptor that gathers elements up in pairs
    let pit = (0..4).batching(|it| match it.next() {
        None => None,
        Some(x) => match it.next() {
            None => None,
            Some(y) => Some((x, y)),
        },
    });

    itertools::assert_equal(pit, vec![(0, 1), (2, 3)]);
}
0
On

This is a solution that creates two iterators, one for odd lines and one for even lines. Then combine the two using .zip(), which gives an iterator filled with pairs:

fn main() {
    let input: Vec<_> = (0..12).collect();

    let it1 = input
        .chunks(3)
        .enumerate()
        .filter_map(|x| if x.0 % 2 == 0 { Some(x.1) } else { None });
    let it2 = input
        .chunks(3)
        .enumerate()
        .filter_map(|x| if x.0 % 2 != 0 { Some(x.1) } else { None });

    let r: Vec<_> = it1.zip(it2).collect();

    println!("{:?}", r);
}