如何在迭代集合的同时修改集合?
How can I modify a collection while also iterating over it?
我有一个 Board
(a.k.a. &mut Vec<Vec<Cell>>
),我想在迭代时更新它。我要更新的新值来自一个函数,该函数需要 &Vec<Vec<Cell>>
到我正在更新的集合。
我试过几种方法:
使用 board.iter_mut().enumerate()
和 row.iter_mut().enumerate()
以便我可以更新最内层循环中的 cell
。 Rust 不允许调用 next_gen
函数,因为它需要一个 &Vec<Vec<Cell>>
并且当你已经有一个可变引用时你不能有一个不可变引用。
更改 next_gen
函数签名以接受 &mut Vec<Vec<Cell>>
。 Rust 不允许对一个对象的多个可变引用。
我目前正在将所有更新推迟到 HashMap
,然后在我执行迭代后应用它们:
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;
}
}
有没有一种方法可以使此代码更新 board
"in place",即在最内层循环内同时仍然能够在最内层循环内调用 next_gen
?
免责声明:
我正在学习 Rust,我知道这不是最好的方法。我正在玩耍,看看我能做什么,不能做什么。我也试图限制任何复制来限制我自己。作为 .
此代码旨在衡量几件事:
- 如果这甚至可能的话
- 如果是惯用的 Rust
根据我在评论中收集到的信息,std::cell::Cell
是可能的。但是,使用 std:cell:Cell
规避了一些核心 Rust 原则,我在原始问题中将其描述为我的 "dilemma"。
Is there a way that I could make this code update the board "in place"?
存在一种专门用于此类情况的类型。它恰巧被称为 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);
}
这完全取决于您的 next_gen
功能。假设我们除了签名之外对该函数一无所知,最简单的方法是使用索引:
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;
}
}
}
}
根据有关 next_gen
的更多信息,可能会有不同的解决方案,但对我来说这听起来很像元胞自动机,据我所知,这不能以迭代器方式完成Rust 不改变 Board
.
的类型
您可能担心索引解决方案的效率不如迭代器解决方案,但在这一点上您应该相信 LLVM。如果你的 next_gen
函数在另一个 crate 中,你应该将它标记为 #[inline]
这样 LLVM 也可以优化它(如果所有东西都在一个 crate 中则没有必要)。
不是你问题的答案,而是你的问题:
由于您正在实施 Conway 的生命游戏,因此您无法就地进行修改。想象一下以下模式:
00000
00100
00100
00100
00000
如果您更新第 2 行,它将将该行中的 1
更改为 0
,因为它的邻域中只有两个 1
。这将导致中间的 1
只看到两个 1
而不是开始时的三个。因此,您总是需要复制整个 Board
,或者像您在代码中所做的那样,将所有更改写入其他位置,并在遍历整个板后将它们拼接起来。
我有一个 Board
(a.k.a. &mut Vec<Vec<Cell>>
),我想在迭代时更新它。我要更新的新值来自一个函数,该函数需要 &Vec<Vec<Cell>>
到我正在更新的集合。
我试过几种方法:
使用
board.iter_mut().enumerate()
和row.iter_mut().enumerate()
以便我可以更新最内层循环中的cell
。 Rust 不允许调用next_gen
函数,因为它需要一个&Vec<Vec<Cell>>
并且当你已经有一个可变引用时你不能有一个不可变引用。更改
next_gen
函数签名以接受&mut Vec<Vec<Cell>>
。 Rust 不允许对一个对象的多个可变引用。
我目前正在将所有更新推迟到 HashMap
,然后在我执行迭代后应用它们:
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;
}
}
有没有一种方法可以使此代码更新 board
"in place",即在最内层循环内同时仍然能够在最内层循环内调用 next_gen
?
免责声明:
我正在学习 Rust,我知道这不是最好的方法。我正在玩耍,看看我能做什么,不能做什么。我也试图限制任何复制来限制我自己。作为
此代码旨在衡量几件事:
- 如果这甚至可能的话
- 如果是惯用的 Rust
根据我在评论中收集到的信息,std::cell::Cell
是可能的。但是,使用 std:cell:Cell
规避了一些核心 Rust 原则,我在原始问题中将其描述为我的 "dilemma"。
Is there a way that I could make this code update the board "in place"?
存在一种专门用于此类情况的类型。它恰巧被称为 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);
}
这完全取决于您的 next_gen
功能。假设我们除了签名之外对该函数一无所知,最简单的方法是使用索引:
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;
}
}
}
}
根据有关 next_gen
的更多信息,可能会有不同的解决方案,但对我来说这听起来很像元胞自动机,据我所知,这不能以迭代器方式完成Rust 不改变 Board
.
您可能担心索引解决方案的效率不如迭代器解决方案,但在这一点上您应该相信 LLVM。如果你的 next_gen
函数在另一个 crate 中,你应该将它标记为 #[inline]
这样 LLVM 也可以优化它(如果所有东西都在一个 crate 中则没有必要)。
不是你问题的答案,而是你的问题:
由于您正在实施 Conway 的生命游戏,因此您无法就地进行修改。想象一下以下模式:
00000
00100
00100
00100
00000
如果您更新第 2 行,它将将该行中的 1
更改为 0
,因为它的邻域中只有两个 1
。这将导致中间的 1
只看到两个 1
而不是开始时的三个。因此,您总是需要复制整个 Board
,或者像您在代码中所做的那样,将所有更改写入其他位置,并在遍历整个板后将它们拼接起来。