anderspitman
anderspitman

Reputation: 10520

Efficiently mutate a vector while also iterating over the same vector

I have a vector of structs, and I'm comparing every element in the vector against every other element, and in certain cases mutating the current element.

My issue is that you can't have both a mutable and immutable borrow happening at the same time, but I'm not sure how to reframe my problem to get around this without cloning either the current element or the entire vector, which seems like a waste since I'm only ever mutating the current element, and it doesn't need to be compared to itself (I skip that case).

I'm sure there's an idiomatic way to do this in Rust.

struct MyStruct {
    a: i32,
}

fn main() {
    let mut v = vec![MyStruct { a: 1 }, MyStruct { a: 2 }, MyStruct { a: 3 }];

    for elem in v.iter_mut() {
        for other_elem in v.iter() {
            if other_elem.a > elem.a {
                elem.a += 1;
            }
        }
    }
}

Upvotes: 14

Views: 8533

Answers (2)

Nicholas Rishel
Nicholas Rishel

Reputation: 1319

If you can modify type type of v, and the elements of v are Copy, you can wrap MyStruct in Cell.

#[derive(Copy, Clone)] 
struct MyStruct {
    a: i32,
}

fn main() {
    use std::cell::Cell;
    let v = vec![
        Cell::new(MyStruct { a: 1 }),
        Cell::new(MyStruct { a: 2 }),
        Cell::new(MyStruct { a: 3 }),
    ];

    for elem in v.iter() {
        for other_elem in v.iter() {
            let mut e = elem.get();
            if other_elem.get().a > e.a {
                e.a += 1;
                elem.set(e);
            }
        }
    }
}

If instead you're passed a &mut to a slice (or &mut that can be converted into a slice), use Cell::from_mut and Cell::as_slice_of_cells and use the same trick as above (assuming the elements of the slice are Copy).

Upvotes: -1

DK.
DK.

Reputation: 58995

The simplest way is to just use indices, which don't involve any long-lived borrows:

for i in 0..v.len() {
    for j in 0..v.len() {
        if i == j { continue; }
        if v[j].a > v[i].a {
            v[i].a += 1;
        }
    }
}

If you really, really want to use iterators, you can do it by dividing up the Vec into disjoint slices:

fn process(elem: &mut MyStruct, other: &MyStruct) {
    if other.a > elem.a {
        elem.a += 1;
    }
}

for i in 0..v.len() {
    let (left, mid_right) = v.split_at_mut(i);
    let (mid, right) = mid_right.split_at_mut(1);
    let elem = &mut mid[0];

    for other in left {
        process(elem, other);
    }
    for other in right {
        process(elem, other);
    }
}

Upvotes: 22

Related Questions