2017-04-17 85 views
1

我以純粹的OO方式實現了Ruby中的二叉樹迷宮代。我試圖在Elixir中將其重寫爲學習練習,但我遇到了OO與FP範例的一些問題。Elixir中的二叉樹迷宮代

我渲染一個包含單元格的網格。當使用二叉樹算法在網格中行走時,對於我決定與其旁邊的北部或東部單元格連接的每個單元格。這種鏈接在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 

但後來雙向通話給我麻煩。我可以在沒有問題的情況下進行調用,但由於我正在處理不可變數據,因此我將永遠無法使用正確的鏈接數組獲取「新」鄰近單元的引用。

爲每個單元實現GenServer似乎有點像我的反模式。肯定必須有一種方法來以純粹的功能方式來實現這種行爲;我是新來的FP,但會喜歡一些幫助。

回答

1

在將OO映射到順序Elixir(一般的函數式語言)時,您可以使用On模式,您可以創建一個數據對象(不是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)> 
官能溶液