用于生命游戏的 OCaml 二维数组
OCaml 2D array for Game of Life
我正在尝试做 Conway 的生命游戏来练习,因为我以前用 C++ 做过这个,但我想知道如何在 Ocaml 中生成一个给定高度和宽度的二维数组,并随机分配生成基于给定的人口密度,我正在尝试使用make_matrix。我在网上找到的所有教程都使用图形或其他一些硬编码方式(即 rosetta 代码),但我想尝试避免这种情况,以便我可以有一些变化。谢谢。
这是我当前的代码,它返回未绑定的值。
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;
这里有多项选择:
- 大数组
- 数组
- 地图
双数组的值只能是数字,但它们可以是任意大小和维数。它们还有一个用于访问多维数据的漂亮界面。数组,可以有任何类型的值,你可以创建数组的数组来模拟多维空间。数组和双数组都是命令式数据结构,您的代码也是。这就是为什么我建议您使用持久映射,使您的代码纯函数化(如果您想在 OCaml 中练习,最好在其功能子空间中练习)。
地图示例
那么,让我们为状态和坐标定义一个类型:
type state = Dead | Live
type coord = {x : int; y : int}
由于我们将使用地图,因此不需要未填充状态。无人居住的州只是未映射。
现在,我们可以定义棋盘数据结构的实现了:
module Board = Map.Make(struct
type t = coord
let compare = compare
end)
作为用法示例,让我们定义一个名为 fold_neighbors
的高阶函数,它将把用户提供的函数应用于每个已填充的相邻单元格。
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
使用这个通用迭代器函数,我们可以定义专门的函数,例如 count_live_neighbors
:
let count_live_neighbors =
fold_neighbors ~init:0 ~f:(fun count nb -> match nb with
| Live -> count + 1
| Dead -> count)
实现假设一个无限的棋盘,如果你想让它有界,那么你需要调整fold_neighbors
函数来排除那些躺在棋盘外面的人。
数组示例
另一种选择是使用普通数组。我们可以使用一个方便的 make_matrix
函数来创建一个给定大小的二维数组并用提供的值填充它,例如
make_matrix 300 400 0
将创建一个 300 行和 400 列的零填充矩阵。
在我们的例子中,我们不想用数字填充矩阵,而是用状态填充矩阵。我们将需要一个状态类型,它能够表示 3 个状态,我们将重用前面示例中的 state
类型,但我们还将其包装到 option
类型中,以便一个单元格是死的还是活的将表示为 Some Dead
或 Some Live
,而未填充的将只是 None
,因此我们可以创建一个空板
Array.make_matrix width height None
要初始化我们的电路板,我们可以先创建它,然后根据所需的人口密度为随机选择的单元格赋予生命。我们将用 0 到 1 之间的浮点数表示密度,例如,密度 0.1
表示大约 10% 的细胞是活的。为了保持更多的功能,我们将使用 Array.map
来转换我们的数组。带有就地修改的显式循环和迭代当然会更快、更惯用,但为了实验起见,让我们使用更函数式的方法:
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))
但是,如果我们不首先创建一个空板,而是从初始化的板开始,我们可以做得更好。为此,我们可以使用 Array.init
函数,它允许为每个单元格赋予不同的值,这里是更好的 create_board
函数的示例:
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))
Array.init
函数使用元素的索引调用用户提供的函数。在我们的例子中,生命的概率不依赖于坐标,所以我们可以忽略位置,这就是为什么我们使用 _
来指定,我们不会使用参数。
我正在尝试做 Conway 的生命游戏来练习,因为我以前用 C++ 做过这个,但我想知道如何在 Ocaml 中生成一个给定高度和宽度的二维数组,并随机分配生成基于给定的人口密度,我正在尝试使用make_matrix。我在网上找到的所有教程都使用图形或其他一些硬编码方式(即 rosetta 代码),但我想尝试避免这种情况,以便我可以有一些变化。谢谢。
这是我当前的代码,它返回未绑定的值。
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;
这里有多项选择:
- 大数组
- 数组
- 地图
双数组的值只能是数字,但它们可以是任意大小和维数。它们还有一个用于访问多维数据的漂亮界面。数组,可以有任何类型的值,你可以创建数组的数组来模拟多维空间。数组和双数组都是命令式数据结构,您的代码也是。这就是为什么我建议您使用持久映射,使您的代码纯函数化(如果您想在 OCaml 中练习,最好在其功能子空间中练习)。
地图示例
那么,让我们为状态和坐标定义一个类型:
type state = Dead | Live
type coord = {x : int; y : int}
由于我们将使用地图,因此不需要未填充状态。无人居住的州只是未映射。
现在,我们可以定义棋盘数据结构的实现了:
module Board = Map.Make(struct
type t = coord
let compare = compare
end)
作为用法示例,让我们定义一个名为 fold_neighbors
的高阶函数,它将把用户提供的函数应用于每个已填充的相邻单元格。
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
使用这个通用迭代器函数,我们可以定义专门的函数,例如 count_live_neighbors
:
let count_live_neighbors =
fold_neighbors ~init:0 ~f:(fun count nb -> match nb with
| Live -> count + 1
| Dead -> count)
实现假设一个无限的棋盘,如果你想让它有界,那么你需要调整fold_neighbors
函数来排除那些躺在棋盘外面的人。
数组示例
另一种选择是使用普通数组。我们可以使用一个方便的 make_matrix
函数来创建一个给定大小的二维数组并用提供的值填充它,例如
make_matrix 300 400 0
将创建一个 300 行和 400 列的零填充矩阵。
在我们的例子中,我们不想用数字填充矩阵,而是用状态填充矩阵。我们将需要一个状态类型,它能够表示 3 个状态,我们将重用前面示例中的 state
类型,但我们还将其包装到 option
类型中,以便一个单元格是死的还是活的将表示为 Some Dead
或 Some Live
,而未填充的将只是 None
,因此我们可以创建一个空板
Array.make_matrix width height None
要初始化我们的电路板,我们可以先创建它,然后根据所需的人口密度为随机选择的单元格赋予生命。我们将用 0 到 1 之间的浮点数表示密度,例如,密度 0.1
表示大约 10% 的细胞是活的。为了保持更多的功能,我们将使用 Array.map
来转换我们的数组。带有就地修改的显式循环和迭代当然会更快、更惯用,但为了实验起见,让我们使用更函数式的方法:
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))
但是,如果我们不首先创建一个空板,而是从初始化的板开始,我们可以做得更好。为此,我们可以使用 Array.init
函数,它允许为每个单元格赋予不同的值,这里是更好的 create_board
函数的示例:
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))
Array.init
函数使用元素的索引调用用户提供的函数。在我们的例子中,生命的概率不依赖于坐标,所以我们可以忽略位置,这就是为什么我们使用 _
来指定,我们不会使用参数。