What is the best way to create an array/tuple by applying a transformation to another array

How do I convert an array of size N, into another array of size N (or a tuple with N elements, I don't care) by applying a function to all of the elements of the source one by one?

Here are a few solutions that I found, but I'm not really satisfied. TL; DR: I think the last one is the best one.


fn f(x: usize) -> usize {
    x*x
}

let source = [1, 2, 3];
let destination = [
    f(source[0]),
    f(source[1]),
    f(source[2]),
]

This feels very verbose and error prone.


let source = [1, 2, 3];
let dest: [usize; 3] = source.iter().map(|x| x*x).collect();

This doesn't compiles.


Using tuples_windows from the itertools crate.

use itertools::Itertools;
let source = [1, 2, 3];
let (a, b, c) = source.iter().map(|x| f(x)).tuple_windows().nth(0).unwrap();

This doesn't feels that much readable.


let source = [1, 2, 3];
let dest: [i32; 3];
for i in 0..source.len() {
    dest[i] = f(source[i])
}

This doesn't compiles.


let mut dest = [0; 3];
for i in 0..source.len() {
    dest[i] = f(source[i])
}

I think it's the best one, even if it is quite verbose.


What should I use?

If you really need it the result to be a [i32; 3], then yes, your last version is the way to do it in general. For such short arrays as 3 items, i think the first is good as well.

What i woud use is:

let source = [1, 2, 3];
let dest: Vec<usize> = source.iter().map(|x| x*x).collect().

Then dest is a dynamically allocated Vec and not really an array, but you can still borrow it as a slice, so it only matters if you have a strong reason to avoid allocations.

1 Like

What I dislike with my last version is that the variable must be mut, and the compiler doesn't verify if all elements are initialized with a sensible value. The before the last version would be much better, but unfortunately it doesn't compiles (yet).

And yes the version with Vec is good too, but I definitively doesn't like to allocate for no reason (especially since Vec doesn't have small buffer optimization, so it will always allocate). It's the kind of small performance degradation that will never be fixed, but will slowly accumulate over time.

The itertools crate also has collect_tuple() method for homogeneous tuples length up to 4.

3 Likes

A variant of the last one:

let source = [1, 2, 3];
let dest = {
    let mut dest = [0; 3];
    for (i, value) in source.iter().enumerate() {
        dest[i] = f(value);
    }
    dest
};

The differences is that dest is only mutable in the initializing block, and that the enumerate() removes the need for indexing source. If that is better or not is probably a matter of tast, as dest is still indexed.

1 Like

arrayvec offers an alternative here, as ArrayVec implements FromIterator:

let source = [1, 2, 3];
let dest: [i32; 3] = source
    .iter()
    .map(|x| f(x))
    .collect::<arrayvec::ArrayVec<_>>()
    .into_inner()
    .expect("this ArrayVec always has length 3");

(playground)

I generally find them much nicer to work with than arrays, and you can convert back and forth (as long as the arrayvec is full, at least, which it will be when you iterate like this).

3 Likes

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.