Iterate over Vector and move some items into another Vector

Hello i am stuck with some obvious beginner problem.

I have two vectors and i want to iterate over one of them and if an item fulfills a condition it should be moved into the other vector.

I googled my problem and found many vector functions that do not do what i want.

For example retain deletes the item forever.

In my example i have entities that are saved in a vector for each chunk. If one entity runs over the border of a chunk it should be moved into the vector of 'chunkless_entities'. It will then be sorted back into a chunk later.

  //take a chunkless entity and put it into the correct chunk
  fn enter_chunk(&mut self, free_entity:Entity){

    let chunk_x = free_entity.pos_x as usize/self.chunk_size;
    let chunk_y = free_entity.pos_y as usize/self.chunk_size;
    //depending on color push into correct chunk array
    if free_entity.color == RED{
      self.chunks_hunt[[chunk_x, chunk_y]].push(free_entity);
    }else{
      self.chunks_prey[[chunk_x, chunk_y]].push(free_entity);
    }
  }

  // remove entities from wrong chunks
  fn leave_chunk(&mut self){
    let mut chunkless_entities = Vec::<Entity>::new();

    //for each chunk
    for x in 0..1+self.w/self.chunk_size{
      for y in 0..1+self.h/self.chunk_size{
        //for each entity
        self.chunks_prey[[x,y]].iter().for_each(|e|{
          //check if it left the chunk
          if (e.pos_x as usize) < x*self.chunk_size || (e.pos_x as usize) > (x+1)*self.chunk_size ||
             (e.pos_y as usize) < y*self.chunk_size || (e.pos_y as usize) > (y+1)*self.chunk_size {
              //entity is not inside the chunk bounds
              //now we move it somehow into another vector?

          }
        });
      }
    }

    //put the lost entities back into a chunk where they belong
    for i in 0..chunkless_entities.len(){
      self.enter_chunk(chunkless_entities.pop().unwrap());
    }
  }

It looks like an API close to your needs would be Vec::drain_filter(), sadly that part of the stdlib API hasn't been stabilized yet.

You can, however, use the suggestion in the documentation of that method and use indices:

let chunk = &mut self.chunks_prey[[x, y]];
let mut i = 0;
while i < chunk.len() {
    let e = &chunk[i];
    if e... {
        let taken_e = chunk.swap_remove(i);
        chunkless_entities.push(taken_e);
    } else {
        i += 1;
    }
}

// ...

for e in chunkless_entities {
    self.enter_chunk(e);
}

Obviously if the order of your entities within a chunk matters, you should use .remove(i) rather than .swap_remove(i), but it doesn't seem to be your case, I suspect.

4 Likes

Another approach at the cost of more allocation would be to use Iterator::partition:

// roughly
let original_vec = std::mem::take(&mut self.chunks_prey);
let (one, two): (Vec<_>, Vec<_>)
  = original_vec.into_iter().partition(|item| ...);
self.chunks_prey = two;
2 Likes

For mutability, my favorite way I've found (while not idiomatic)to handle a situation like this is to use another Vec to collect the index of each item that should be updated while looping through the collection. From here, you have a collection of entity indices which are out of bounds which you can loop over and modify the Vec without angering the borrow checker, or having to rebuild the whole thing:

let mut entity_indices = vec![];

//for each chunk
for x in 0..1+self.w/self.chunk_size{
  for y in 0..1+self.h/self.chunk_size{
    //for each entity
    self.chunks_prey[[x,y]].iter().enumerate().for_each(|(i, e)|{
      //check if it left the chunk
      if (e.pos_x as usize) < x*self.chunk_size || (e.pos_x as usize) > (x+1)*self.chunk_size ||
          (e.pos_y as usize) < y*self.chunk_size || (e.pos_y as usize) > (y+1)*self.chunk_size {
            // add the entity's index to the collection
            entity_indices.push(i);
      }
    });
  }
}

for i in entity_indices {
  // ...
}
1 Like

A quite efficient and simple way is to perform the partition in-place, then drain() the second half of the vector: Playground.

(The slice-only method I provided will have a std alternative in the future, Iterator::partition_in_place().)

4 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.