Batuhan Aydın
Batuhan Aydın

Reputation: 53

Remove elements of vector in a loop based on index

Let's say I have a vector, the values are from 1 to 10. I want that if you find 5 and 5 next to each other, remove them together with the next elements.

input

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

expected output

[1, 2, 3, 4] 

This was my attempt. I'm finding index to remove, but borrowing rules are making me stuck.

let mut element = vec![1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10];
for (index, val) in element.iter().enumerate() {
    if *val == 5 {
        if let Some(next_val) = element.get(index + 1) {
            if *next_val == 5 {
                //element.drain(index..);
            }
        }
    }
}

Upvotes: 2

Views: 2261

Answers (2)

asmmo
asmmo

Reputation: 7100

You can't do what you want to do because you want to remove some elements from a vector while you are iterating it. And this is a big mistake. Note that removing any elements from a vector invalidates the iterators, hence you will access unexpected locations so rust doesn't allow UBs You can use something like the following

    let mut elements = vec![1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10];
    let mut first_repeated_five_index_op = None;
    for index in 0..elements.len()-1{
        if elements[index] == 5 && elements[index + 1] == 5{
            first_repeated_five_index_op = Some(index);
            break;
        }
    }
    if let Some(first_repeated_five_index) = first_repeated_five_index_op{ 
          elements.truncate(first_repeated_five_index);
    }
    println!("{:?}", elements);

See a Demo

Upvotes: 2

trent
trent

Reputation: 27905

Rust is saving you from iterator invalidation (a common source of bugs in other languages). This is an error that usually happens when you try to modify a data structure while concurrently iterating over it. You cannot move on to the (now-deleted) next element after calling element.drain(index..). So you need to add a break after that point to avoid memory unsafety.

In this case just adding break; is sufficient to make the code compile. However, for a more concise, linear solution, take full advantage of the iterators and methods provided by the standard library:

if let Some(index) = element.windows(2).position(|pair| pair[0] == pair[1]) {
    element.truncate(index);
}

windows(2) on a slice gives an iterator over subslices of length 2, and the position call returns the index of the first element of that iterator for which the two elements of the slice are equal. (If no such pair exists, position returns None.)

I find that the position closure becomes more obvious with the (currently unstable) array_windows feature:

if let Some(index) = element.array_windows().position(|[x, y]| x == y) {
    element.truncate(index);
}

Playground

Related

Upvotes: 9

Related Questions