N-ary Iterator transposition (`unzip()`)

Hi,

I have a some data [[A, B, C], ...] that I want to transpose into [[A, ...], [B, ...], [C, ...]]. It seems like Iterator::unzip() does what I want for the 2-ary case of just A and B, but not for arbitrary N-ary.

One approach would be to just use N iterators, each using nth(0..N) on the inner arrays. Another approach would be to use Iterator::flatten() + N x Iterator::skip(0..N) + Iterator::step_by(N) iterator objects.

Does anything better exist?

Thanks!

Edit: I found this crate, which seems to do something like what I would like: https://crates.io/crates/unzip-n . Although it seems to collect the resulting values into a concrete container, rather than producing iterators. That's ok.

Did you consider a loop?

let mut outs = vec![vec![]; 3];
for [a,b,c] in iter {
    outs[0].push(a);
    outs[1].push(b);
    outs[2].push(c);
}

Sure, it is easy enough to unroll manually for a fixed size. I am looking for a generic abstraction for an arbitrary number of elements. (The unzip-n crate does this.) Additionally, I would like to iterate the elements, rather than constructing concrete vectors. (Unzip-n does not do this.) Thanks!

It’s unclear whether your iterator contains tuples or arrays. Please clarify.

Iterators don’t come with any memory. If you unzip an iterator, you’ll have to store the elements somewhere – without storing them, you’d be required to process them (more or less) exactly in the order they arrive. I.e. you couldn’t, in something like [(A1, B1), (A2, B2), (A3, B3), …] first iterate over all the As and then over all the Bs. Thus you cannot produce two separate iterators either, since those would necessarily allow you to do just that (first iterator over one, then the other).


Edit: Ah, wait… I didn’t really read this part of the question

So it sounds like arrays, not tuples. The potential confusion comes from the fact that Iterator::unzip is about tuples (of 2 elements).

Also, this sounds like you’re iterating by reference, in which case you can clone the iterator itself and thus avoid the problem of needing to store the iterator itself. So creating iterators instead of collecting into Vecs should indeed be possible. The only question I have left now is whether you have actual arrays of vectors as the elements. I.e. is the item type [T; N] for some number N or is it Vec<T>?

E.g. for arrays, you can generically implement what’s basically the

approach. E.g.

fn unzip<'a, T: 'a, I, const N: usize>(iter: I) -> [impl Iterator<Item = &'a T>; N]
where
    I: IntoIterator<Item = &'a [T; N]>,
    I::IntoIter: Clone,
{
    let iter = iter.into_iter();
    let mut i = 0;
    [(); N].map(|_| {
        let index = i;
        i += 1;
        iter.clone().map(move |arr| &arr[index])
    })
}

fn main() {
    let x = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]];
    let [a, b, c] = unzip(&x);
    for i in a {
        println!("a {}", i);
    }
    for i in b {
        println!("b {}", i);
    }
    for i in c {
        println!("c {}", i);
    }
}

(playground)


(For vectors on the other hand, one would first need to answer the question what to do when different items have different lengths.)

1 Like

Honestly I would just go for two nested loops then.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.