Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I modify a collection while also iterating over it?

I have a Board (a.k.a. &mut Vec<Vec<Cell>>) which I would like to update while iterating over it. The new value I want to update with is derived from a function which requires a &Vec<Vec<Cell>> to the collection I'm updating.

I have tried several things:

  1. Use board.iter_mut().enumerate() and row.iter_mut().enumerate() so that I could update the cell in the innermost loop. Rust does not allow calling the next_gen function because it requires a &Vec<Vec<Cell>> and you cannot have a immutable reference when you already have a mutable reference.

  2. Change the next_gen function signature to accept a &mut Vec<Vec<Cell>>. Rust does not allow multiple mutable references to an object.

I'm currently deferring all the updates to a HashMap and then applying them after I've performed my iteration:

fn step(board: &mut Board) {
    let mut cells_to_update: HashMap<(usize, usize), Cell> = HashMap::new();
    for (row_index, row) in board.iter().enumerate() {
        for (column_index, cell) in row.iter().enumerate() {
            let cell_next = next_gen((row_index, column_index), &board);
            if *cell != cell_next {
                cells_to_update.insert((row_index, column_index), cell_next);
            }
        }
    }

    println!("To Update: {:?}", cells_to_update);
    for ((row_index, column_index), cell) in cells_to_update {
        board[row_index][column_index] = cell;
    }
}

Full source

Is there a way that I could make this code update the board "in place", that is, inside the innermost loop while still being able to call next_gen inside the innermost loop?

Disclaimer:

I'm learning Rust and I know this is not the best way to do this. I'm playing around to see what I can and cannot do. I'm also trying to limit any copying to restrict myself a little bit. As oli_obk - ker mentions, this implementation for Conway's Game of Life is flawed.

This code was intended to gauge a couple of things:

  1. if this is even possible
  2. if it is idiomatic Rust

From what I have gathered in the comments, it is possible with std::cell::Cell. However, using std:cell:Cell circumvents some of the core Rust principles, which I described as my "dilemma" in the original question.

like image 620
arnm Avatar asked Jun 21 '15 16:06

arnm


People also ask

Can one thread modify a collection while another is iterating over it?

It is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances.

How to remove elements from a collection while iterating over it?

Actually, the right way to handle such scenario is to use Iterator to remove the element from the underlying Collection while iterating over it.

Is it possible to add or remove elements from a collection?

This is not permitted in Apex. Look at collections being read only whilst iterating over them, though you can modify existing elements in the collection, but you can't add or remove elements from it. You say I can modify existing elements, I belive that is what I am trying to achieve here.

How to add elements to an opptys2 collection?

To answer the first part of your question, you are modifying (adding elements) to the Collection Opptys2, whilst iterating over it. This is not permitted in Apex. Look at collections being read only whilst iterating over them, though you can modify existing elements in the collection, but you can't add or remove elements from it.


2 Answers

Is there a way that I could make this code update the board "in place"?

There exists a type specially made for situations such as these. It's coincidentally called std::cell::Cell. You're allowed to mutate the contents of a Cell even when it has been immutably borrowed multiple times. Cell is limited to types that implement Copy (for others you have to use RefCell, and if multiple threads are involved then you must use an Arc in combination with somethinng like a Mutex).

use std::cell::Cell;

fn main() {
    let board = vec![Cell::new(0), Cell::new(1), Cell::new(2)];

    for a in board.iter() {
        for b in board.iter() {
            a.set(a.get() + b.get());
        }
    }
    println!("{:?}", board);
}
like image 100
A.B. Avatar answered Oct 13 '22 13:10

A.B.


It entirely depends on your next_gen function. Assuming we know nothing about the function except its signature, the easiest way is to use indices:

fn step(board: &mut Board) {
    for row_index in 0..board.len() {
        for column_index in 0..board[row_index].len() {
            let cell_next = next_gen((row_index, column_index), &board);
            if board[row_index][column_index] != cell_next {
                board[row_index][column_index] = cell_next;
            }
        }
    }
}

With more information about next_gen a different solution might be possible, but it sounds a lot like a cellular automaton to me, and to the best of my knowledge this cannot be done in an iterator-way in Rust without changing the type of Board.

You might fear that the indexing solution will be less efficient than an iterator solution, but you should trust LLVM on this. In case your next_gen function is in another crate, you should mark it #[inline] so LLVM can optimize it too (not necessary if everything is in one crate).


Not an answer to your question, but to your problem:

Since you are implementing Conway's Game of Life, you cannot do the modification in-place. Imagine the following pattern:

00000
00100
00100
00100
00000

If you update line 2, it will change the 1 in that line to a 0 since it has only two 1s in its neighborhood. This will cause the middle 1 to see only two 1s instead of the three that were there to begin with. Therefor you always need to either make a copy of the entire Board, or, as you did in your code, write all the changes to some other location, and splice them in after going through the entire board.

like image 40
oli_obk Avatar answered Oct 13 '22 13:10

oli_obk