Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to concatenate immutable vectors in one line?

I have immutable vectors a and b where the elements are cheap to copy and I want to create a vector that forms the concatenation of these existing vectors without changing them (*).

An earlier question addressed it if one of the vectors is mutable, so an obvious answer is to first clone vector a, e.g.

let mut r = a.clone();
r.extend(&b);

but that seems neither elegant nor efficient (extending can easily cause one needless reallocation, right?). The (corrected) best alternative I (being a Rust noob) come up with is:

fn cat(a: &Vec<i32>, b: &Vec<i32>) -> Vec<i32> {
    let mut r = Vec::<i32>::with_capacity(a.len() + b.len());
    r.extend(a);
    r.extend(b);
    r
}

Since elements are cheap to copy, the answer to the more generic question for vectors of strings should apply here, but vec![a, b].concat() only seems to work if you construct the vector of vectors by moving the vectors into it, because vec![&a, &b].concat()yields "no method named concat found".

Is there a one-liner for this seemingly simple job, even if it's not optimal?


(*) turns out there are two meanings to "without changing":

  • just immutable, which in Rust means that if code compiles, it will not see the variable with a changed value; but the variable may be moved out, and further or future code cannot use it anymore
  • actually read-only, leaving the variable untouched for further or future code
like image 416
Stein Avatar asked Nov 25 '18 16:11

Stein


3 Answers

concat does work, if used correctly:

fn cat(a: &[i32], b: &[i32]) -> Vec<i32> {
    [a, b].concat()
}

fn main() {
    let a = vec![1, 2, 3];
    let b = vec![7, 8, 9];
    println!("{:?}", cat(&a, &b));
}
like image 192
hellow Avatar answered Oct 21 '22 09:10

hellow


Arrays can concat() slices to a vector, you just need to give a stronger hint to the compiler:

let combined = [a.as_slice(), b.as_slice()].concat();

Here's a rust playground to try it out. You can see that neither a nor b are consumed, and combined is a new vector. Note that this fails if you try the & shorthand for borrowing instead of specifying as_slice().

like image 38
carver Avatar answered Oct 21 '22 07:10

carver


Editor's note: The OP changed their question after this answer was provided. Refer to the version of the question this answer was created from.

Your first example doesn't really make sense. You mention immutability, but since you are transferring ownership of the vectors to the cat function, it can choose what the mutability is of the variables. In this case, you might as well just reuse the allocation of one of them:

fn cat(mut a: Vec<i32>, b: Vec<i32>) -> Vec<i32> {
    a.extend(b);
    a
}

extending can easily cause needless reallocation

This is technically possible, but extremely unlikely. There's a reason that iterators have the method size_hint — this allows collections to allocate exactly whenever possible.

a one-liner

a.into_iter().chain(b).collect::<Vec<_>>()

This destroys the allocation of the vectors a and b (not of the elements inside them) and creates a new allocation to hold all the items.


If you had immutable slices, you can use the same technique:

fn cat<T: Clone>(a: &[T], b: &[T]) -> Vec<T> {
    a.iter().chain(b).cloned().collect()
}

See also:

  • Why is it discouraged to accept a reference to a String (&String), Vec (&Vec) or Box (&Box) as a function argument?
like image 27
Shepmaster Avatar answered Oct 21 '22 08:10

Shepmaster