Elixir中的二叉树迷宫生成

时间:2017-04-17 14:20:21

标签: ruby functional-programming elixir immutability

我在Ruby中实现了二进制树迷宫生成,纯粹是OO方式。我试图在Elixir中重写这个作为一个学习练习,但我遇到了OO与FP范式的一些问题。

我渲染一个包含单元格的网格。当使用二叉树算法遍历网格时,对于每个单元格,我决定与其旁边的北部或东部单元格链接。这个链接在Ruby实现中是双向的:

def link(cell, bidirectional=true)
  @links[cell] = true
  cell.link(self, false) if bidirectional
  self
end

def unlink(cell, bidirectional=true)
  @links.delete cell
  cell.unlink(self, false) if bidirectional
  self
end

因此它将小区链接到邻居,并将邻居链接到小区。我无法弄清楚如何在Elixir中做到这一点。我把这个功能的第一部分放下了:

def link(cell, neighbour, bidirectional) do
  %{ cell | links: cell.links ++ [neighbour]}
end



test "it links cells in a bidirectional way" do
  cell = Cell.create(1, 1)
  neighbour = Cell.create(2, 2)

  %{ row: _, column: _, links: cell_links } = Cell.link(cell, neighbour, true)
  assert Enum.member? cell_links, neighbour
  # ?? check if neighbour links includes cell, but cannot get a reference to "new" neighbour
end

然后双向通话给我带来了麻烦。我可以毫无问题地拨打电话,但由于我处理不可变数据,我永远无法获得对#34; new"的引用。具有正确链接数组的相邻单元格。

为每个单元实现GenServer对我来说似乎有点像反模式。肯定必须有一种方法以纯粹的功能方式实现这种行为;我是FP的新手,但我会很乐意帮助他们。

1 个答案:

答案 0 :(得分:1)

在将OO映射到顺序Elixir(通常是函数语言)时可以使用的模式,您可以创建一个数据对象(不是OO对象)并将其作为函数的第一个参数传递。这样,您就可以在每次调用时转换数据。

所以,你的api会形如def link(maze, cell, bidirectional \\ true)。使用地图来表示迷宫,其中{x,y}元组作为键,地图作为值,您可以访问单个单元格并更新它们。

以下是一些未经测试的代码。

def Maze do
  def new, do: %{cells: %{], links: %{}, start: {0,0}}}

  def link(maze, cell1, cell2, bidirectional \\ true) do
    maze
    |> put_in([:links, cell2], true)
    |> link_bidirectional(cell1, bidirectional)
  end

  defp link_bidirectional(maze, _, _, false), do: maze
  defp link_bidirectional(maze, cell1, cell2, _) do
    link(maze, cell2, cell1, false)
  end
end

编辑:这是一个用于链接的功能性解决方案

defmodule Maze do
  def new do 
    %{cells: %{{0, 0} => Cell.create(0,0)}, tree: {{0, 0}, nil, nil}}
  end

  def new_cell(maze, row, column) do
    # ignoring the tree for now
    put_in(maze, [:cells, {row, column}], Cell.create(row, column))
  end

  def link(maze, cell1, cell2, bidirectional \\ true)
  def link(maze, %{} = cell1, %{} = cell2, bidirectional) do
    maze
    |> update_in([:cells, cell1[:origin]], &(Cell.link(&1, cell2)))
    |> do_bidirectional(cell1, cell2, bidirectional, &link/4)
  end
  def link(maze, {_, _} = pt1, {_, _} = pt2, bidirectional) do
    link(maze, maze[:cells][pt1], maze[:cells][pt2], bidirectional)
  end

  def unlink(maze, %{} = cell1, %{} = cell2, bidirectional \\ true) do
    maze
    |> update_in([:cells, cell1[:origin]], &(Cell.unlink(&1, cell2)))
    |> do_bidirectional(cell1, cell2, bidirectional, &unlink/4)
  end

  defp do_bidirectional(maze, _, _, false, _), do: maze
  defp do_bidirectional(maze, cell1, cell2, _, fun) do
    fun.(maze, cell2, cell1, false)
  end
end

defmodule Cell do
  def create(row,column), do: %{origin: {row, column}, links: %{}}
  def link(self, cell) do
    update_in(self, [:links, cell[:origin]], fn _ -> true end)
  end
  def unlink(self, cell) do
    update_in(self, [:links], &Map.delete(&1, cell[:origin]))
  end
end

iex(26)> Maze.new() |>
...(26)> Maze.new_cell(0,1) |>
...(26)> Maze.new_cell(1,0) |>
...(26)> Maze.link({0,0}, {0,1}) |>
...(26)> Maze.link({0,0}, {1,0})
%{cells: %{{0,
     0} => %{links: %{{0, 1} => true, {1, 0} => true}, origin: {0, 0}},
    {0, 1} => %{links: %{{0, 0} => true}, origin: {0, 1}},
    {1, 0} => %{links: %{{0, 0} => true}, origin: {1, 0}}},
  tree: {{0, 0}, nil, nil}}
iex(27)>