【问题标题】:How to create a map in a loop in Elixir如何在 Elixir 中循环创建地图
【发布时间】:2019-01-24 17:10:08
【问题描述】:

我正在创建一个 2d 地图,并希望先用空值预填充它。

我知道以下内容在 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 数组进行,但我认为以坐标 touples 作为键的平面地图更易于使用。

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

【问题讨论】:

    标签: elixir


    【解决方案1】:

    使用 Comprehensions 的一行代码:

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

    【讨论】:

      【解决方案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} => " "}
      

      【讨论】:

      • 正是我所需要的,谢谢!顺便说一句,有没有办法摆脱 end) 位中的括号?
      • 是的,您可以将reduce( 替换为reduce,将end) 替换为end。当参数很长但它们是可选的时,我更喜欢将它们写出来。
      【解决方案3】:

      另一种方法是使用推导式创建列表元组并将其转换为地图。

      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)>
      

      【讨论】:

        【解决方案4】:

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

          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_boundupper_bound

        在 Elixir 中,尽管存在 if 语句,但在函数中使用模式匹配更为惯用,就像我对 draw_tile 所做的那样。

        另外,我认为(个人意见,不确定这是否反映在整个 Elixir 社区中),应该尽可能避免嵌套。

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-07-07
          • 1970-01-01
          • 2021-04-10
          • 1970-01-01
          • 2019-04-09
          • 2020-09-16
          • 2014-06-19
          • 1970-01-01
          相关资源
          最近更新 更多