Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutating a travelling window in a Rust ndarray

I am attempting to implement one iteration of Conway's Game of Life in Rust using the ndarray library.

I thought a 3x3 window looping over the array would be a simple way to count the living neighbours, however I am having trouble doing the actual update.

The array signifies life with # and the absence of life with :

let mut world = Array2::<String>::from_elem((10, 10), " ".to_string());
for mut window in world.windows((3, 3)) {
    let count_all  = window.fold(0, |count, cell| if cell == "#" { count + 1 } else { count });
    let count_neighbours = count_all - if window[(1, 1)] == "#" { 1 } else { 0 };
    match count_neighbours {
        0 | 1   => window[(1, 1)] = " ".to_string(), // Under-population
        2       => {},                               // Live if alive
        3       => window[(1, 1)] = "#".to_string(), // Re-produce
        _       => window[(1, 1)] = " ".to_string(), // Over-population
    }
}

This code does not compile! The error is within the match block with "error: cannot borrow as mutable" and "error: cannot assign to immutable index". I attempted for &mut window... but the library does not implement this(?)

I'm relatively new to Rust and I believe this may be an issue with the implementation of windows by the library. However, I'm not sure and I don't know if there perhaps some variation/fix that allows me to continue with this approach. Do I need to scrap this approach entirely? I'm not sure what the best approach would be here.

Any other suggestions or improvements to the code would be greatly appreciated.

(This code doesn't implement proper rules as I am mutating as I loop and I am ignoring the outer edge, however that is okay in this case. Also, any variations which do these things are also okay - the details are not important.)

like image 949
QasimK Avatar asked Apr 20 '17 19:04

QasimK


1 Answers

Your general approach using ndarray and windows is ok, but the problem is that the values that you get from the windows iterator will always be immutable. You can work around that by wrapping the values in Cell or RefCell, which gives you interior mutability. That is, they wrap a value as if it was immutable, but provide an API to let you mutate it anyway.

Here is your code, fairly brutally adapted to use RefCell:

use ndarray::Array2;
use std::cell::RefCell;

fn main() {
    // creating variables for convenience, so they can be &-referenced
    let alive = String::from("#");
    let dead = String::from(" ");

    let world = Array2::<String>::from_elem((10, 10), " ".to_string());
    let world = world.map(RefCell::new);

    for mut window in world.windows((3, 3)) {
        let count_all  = window.fold(0, |count, cell| if *cell.borrow() == &alive { count + 1 } else { count });
        let count_neighbours = count_all - if *window[(1, 1)].borrow() == &alive { 1 } else { 0 };
        match count_neighbours {
            0 | 1   => *window[(1, 1)].borrow_mut() = &dead,  // Under-population
            2       => {},                                    // Live if alive
            3       => *window[(1, 1)].borrow_mut() = &alive, // Re-produce
            _       => *window[(1, 1)].borrow_mut() = &alive, // Over-population
        }
    }
}

What I've done above is really just to get your code working, pretty much as-is. But, as E_net4 pointed out, your solution has a major bug because it is mutating as it reads. Also, in terms of best-practices, your usage of String isn't ideal. An enum is much better because it's smaller, can be stack-allocated and better captures the invariants of your model. With an enum you would derive Copy as below, which would let you use a Cell instead of RefCell, which is likely to be better performance because it copies the data, instead of having to count references.

#[derive(Debug, PartialEq, Clone, Copy)]
enum CellState {
    Alive,
    Dead
}
like image 187
Peter Hall Avatar answered Oct 26 '22 01:10

Peter Hall