如何在迭代集合的同时修改集合?

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

我有一个 Board (a.k.a. &mut Vec<Vec<Cell>>),我想在迭代时更新它。我要更新的新值来自一个函数,该函数需要 &Vec<Vec<Cell>> 到我正在更新的集合。

我试过几种方法:

  1. 使用 board.iter_mut().enumerate()row.iter_mut().enumerate() 以便我可以更新最内层循环中的 cell。 Rust 不允许调用 next_gen 函数,因为它需要一个 &Vec<Vec<Cell>> 并且当你已经有一个可变引用时你不能有一个不可变引用。

  2. 更改 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;
    }
}

Full source

有没有一种方法可以使此代码更新 board "in place",即在最内层循环内同时仍然能够在最内层循环内调用 next_gen

免责声明

我正在学习 Rust,我知道这不是最好的方法。我正在玩耍,看看我能做什么,不能做什么。我也试图限制任何复制来限制我自己。作为 .

此代码旨在衡量几件事:

  1. 如果这甚至可能的话
  2. 如果是惯用的 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,或者像您在代码中所做的那样,将所有更改写入其他位置,并在遍历整个板后将它们拼接起来。