I'm trying to do Conway's game of life for practice, since I've done this before in C++, but I was wondering how I could generate a 2d array given an height and width in Ocaml with randomly assigned spawns based on a given population density, I am trying to use make_matrix by the way. All the tutorials I found online have been using graphics or some other hard coded way (i.e rosetta code), but I would like to try and avoid that so that I could have some variation. Thank you.
This is my current code and it is returning unbound value.
print_string "Input width ";
let num = read_int () in
print_string "Input height";
let num2 = read_int () in
myArray = Array.make_matrix num num2 0;
error: Unbound value myArray;
You have multiple choices here:
Bigarrays can have only numbers as their values, but they can be of arbitrary sizes and dimensions. They also have a nice interface for accessing multidimensional data. Arrays, can have values of any type, and you can create arrays of arrays to model multidimensional spaces. Both arrays and bigarrays are imperative data structures, so would be your code. That's why I would suggest you to use persistent maps, to make your code pure-functional (if you want to practice in OCaml it is better to practice in its functional subspace).
So, let's define a type for the state and coordinates:
type state = Dead | Live
type coord = {x : int; y : int}
Since we will use a map, we don't need the unpopulated state. Unpopulated states are just unmapped.
Now, we can define the implementation of the board data structure:
module Board = Map.Make(struct
type t = coord
let compare = compare
end)
As an example of usage, let's define a higher order function called fold_neighbors
that will apply a user provided function to each populated neighbor cell.
let neighbors {x;y} = [
x, y+1;
x+1,y+1;
x+1,y;
x+1,y-1;
x, y-1;
x-1,y-1;
x-1,y;
x-1,y+1;
] |> List.map (fun (x,y) -> {x;y})
let fold_neighbors board cell ~f ~init =
neighbors cell |>
List.fold_left (fun acc n ->
try f acc (Board.find n board)
with Not_found -> acc)
init
Using this generic iterator function, we can define specialized functions like count_live_neighbors
:
let count_live_neighbors =
fold_neighbors ~init:0 ~f:(fun count nb -> match nb with
| Live -> count + 1
| Dead -> count)
The implementation assumes an infinite board, if you would like to make it bounded, then you need to adapt fold_neighbors
function to exclude those who lays outside of the board.
Another alternative is to use ordinary arrays. We can use a convenient make_matrix
function that will create a 2d array of the given sizes and fill it with the provided value, e.g.,
make_matrix 300 400 0
will create a zero-filled matrix with 300 rows and 400 columns.
In our case, we don't want to fill the matrix with numbers, but with states. We will need a type for state, that is able to represent 3 states, we will reuse the state
type from the previous example, but we also wrap it into the option
type, so that a cell that is either dead or live will be represented as Some Dead
or Some Live
, and the unpopulated one will be just None
, so we can create an empty board with
Array.make_matrix width height None
To initialize our board we can first create it, and then give a life to randomly chosen cells based on the required density of population. We will represent the density with a floating point number between zero and one, e.g., density 0.1
means that approximately 10% of cells will be live. To stay more functional, we will use Array.map
to transform our arrays. Explicit loops and iterations with inplace modifications would be faster and more idiomatic of course, but just for the sake of experiment let's use more functional approach:
let create_board width height density =
Array.make_matrix width height None |>
Array.map (Array.map (fun cell ->
if Random.float 1.0 > density then Some Live else None))
However, we can do this more nicely, if we will not create an empty board on the first hand, but will start with the initialized board. For this we can use Array.init
function, that allows to give a different value for each cell, here is the example of the better create_board
function:
let create_board width height density =
Array.init height (fun _ ->
Array.init width (fun _ ->
if Random.float 1.0 > density then Some Live else None))
The Array.init
function calls a user provided function with the index of the element. In our case, the probability of life doesn't depend on coordinates, so we can just ignore the position, that's why we used _
to designate, that we're not going to use the argument.