如何在Elixir的循环中创建地图

时间:2017-04-15 19:03:32

标签: elixir

我正在创建一个二维地图,并希望从预填充空值开始。

我知道以下内容在Elixir中不起作用,但这正是我想要做的。

def empty_map(size_x, size_y) do
  map = %{}

  for x <- 1..size_x do
    for y <- 1..size_y do
      map = Map.put(map, {x, y}, " ")
    end
  end
end

然后我将在地图上绘制形状,如

def create_room(map, {from_x, from_y}, {width, height}) do
  for x in from_x..(from_x + width) do
    for y in from_x..(from_x + width) do
      if # first line, or last line, or first col, or last col
        map = Map.replace(map, x, y, '#')
      else
        map = Map.replace(map, x, y, '.')
      end
    end
  end
end

我曾尝试将其作为2D数组进行操作,但我认为使用坐标作为键的平面地图将更容易使用。

我知道我应该使用递归,但我并不知道如何优雅地进行递归,这种情况不断出现,我还没有看到一种简单/通用的方法来做到这一点。 / p>

4 个答案:

答案 0 :(得分:2)

你可以在这里使用两个嵌套的Enum.reduce/3,将地图作为累加器传递,而不是自己编写递归函数:

defmodule A do
  def empty_map(size_x, size_y) do
    Enum.reduce(1..size_x, %{}, fn x, acc ->
      Enum.reduce(1..size_y, acc, fn y, acc ->
        Map.put(acc, {x, y}, " ")
      end)
    end)
  end
end

IO.inspect A.empty_map(3, 4)

输出:

%{{1, 1} => " ", {1, 2} => " ", {1, 3} => " ", {1, 4} => " ", {2, 1} => " ",
  {2, 2} => " ", {2, 3} => " ", {2, 4} => " ", {3, 1} => " ", {3, 2} => " ",
  {3, 3} => " ", {3, 4} => " "}

答案 1 :(得分:1)

另一种方法是创建一个包含理解的列表元组并将其转换为地图。

iex(18)> defmodule Room do
...(18)>   def empty_map(size_x, size_y) do
...(18)>     for x <- 1..size_x, y <- 1..size_y do
...(18)>       {{x,y}, " "}
...(18)>     end
...(18)>     |> Enum.into(%{})
...(18)>   end
...(18)>
...(18)>   def create_room(map, {from_x, from_y}, {width, height}) do
...(18)>     last_x = from_x + width
...(18)>     last_y = from_y + height
...(18)>     for x <- from_x..last_x, y <- from_y..last_y do
...(18)>       if x == from_x or x == last_x or y == from_y or y == last_y,
...(18)>         do: {{x, y}, "#"}, else: {{x, y}, "."}
...(18)>     end
...(18)>     |> Enum.into(map)
...(18)>   end
...(18)> end
warning: redefining module Room (current version defined in memory)
  iex:18

{:module, Room,
 <<70, 79, 82, 49, 0, 0, 10, 244, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 10,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:create_room, 3}}


iex(19)> Room.empty_map(3,4) |> Room.create_room({1,2}, {3,3})
%{{1, 1} => " ", {1, 2} => "#", {1, 3} => "#", {1, 4} => "#", {1, 5} => "#",
  {2, 1} => " ", {2, 2} => "#", {2, 3} => ".", {2, 4} => ".", {2, 5} => "#",
  {3, 1} => " ", {3, 2} => "#", {3, 3} => ".", {3, 4} => ".", {3, 5} => "#",
  {4, 2} => "#", {4, 3} => "#", {4, 4} => "#", {4, 5} => "#"}
iex(20)>

答案 2 :(得分:1)

使用理解的一行:

    for x <- 1..10, y <- 1..10, into: %{}, do: {{x, y}, " "}

答案 3 :(得分:0)

我试图创建自己的解决方案,该解决方案将以一种可以理解但仍尽可能简洁的方式完成上述操作(我自己只是一个初学者)。

  def create_room(left_bound, right_bound, lower_bound, upper_bound) do
    for x <- left_bound..right_bound,
        y <- lower_bound..upper_bound,
        into: %{}
    do
      draw_tile(x, y, border?(x, y, left_bound, right_bound, lower_bound, upper_bound));
    end
  end

  def border?(x, y, left_bound, right_bound, lower_bound, upper_bound) do
    x in [left_bound, right_bound] ||
    y in [lower_bound, upper_bound]
  end

  def draw_tile(x, y, _is_border = true) do
    {{x,y}, "#"}
  end

  def draw_tile(x, y, _is_border = false) do
    {{x,y}, "."}
  end

首先,由于Range是包容性的(即1..4是[1,2,3,4]而不是[1,2,3]),因此我使用实际边界而不是宽度和高度参数。这使我更加清楚我与代码有什么关系。

接下来,我做了一个简单的理解,如@AA。的答案所示,基本上遍历了x和y的所有可能组合。

如果在该循环的主体内返回一个包含两个元素的元组,则该元组的第一个元素将是键,第二个元素将是插入到映射中的值。

在主体内,我使用draw_tile,该函数采用坐标和布尔值,指示图块是否为边界图块。

最后,函数border?仅检查xy是否等于left_boundright_boundlower_bound或{{ 1}}。

在Elixir中,尽管存在upper_bound条语句,但是像在if中一样,在函数中使用模式匹配更为习惯。

此外,我认为(个人观点,不确定是否在整个Elixir社区中得到反映),应尽可能避免嵌套。

编辑: 您还可以在Elixir文档中找到很多有关语法的信息。

例如,请参见Kernel.SpecialForms.for/1Kernel.in/2