【问题标题】:Calculate the bounding box from a list of points using Elixir使用 Elixir 从点列表中计算边界框
【发布时间】:2019-03-19 23:21:44
【问题描述】:

给定一个点列表

[
  %{ x: 3, y: 8 },
  ...,
  %{ x: 1, y: 4 }
]

计算包含所有点的框坐标的最佳方法是什么 - 即边界:%{ x1: 1, y1: 4, x2: 3, y2: 8 }

我有一种预感,我可以使用 Enum.flat_map_reduce/3,但目前语法让我感到困惑。

【问题讨论】:

    标签: functional-programming elixir


    【解决方案1】:

    我实际上在去年 2018 年第 10 天的 Advent of Code 中遇到了这个问题。 This was my method。它将边界框设置为初始点,然后在找到更远的点时将其扩展。不需要nil。 :-)

    这里适用于您的数据:

    # Start with the first point, then compare all the others to find the extremities
    def bounding_box([%{x: x, y: y} | points]) do
      Enum.reduce(points, %{x1: x, y1: y, x2: x, y2: y}, fn point, box ->
        %{
          x1: min(point.x, box.x1),
          y1: min(point.y, box.y1),
          x2: max(point.x, box.x2),
          y2: max(point.y, box.y2)
        }
      end)
    end
    

    【讨论】:

    • 很好,我发现这比 Aleksei 的解决方案更具可读性,尽管我猜该答案中的内部 fn 参数也可以重命名为“point”和“box”,这会有所帮助。如果外部函数是:def bounding_box([%{x: x, y: y} | points]) do ...
    • 谢谢 :) 你是对的。当我最初编写它时,我考虑过这一点,但假设它在单个元素的情况下不起作用,因为它将一个空列表传递给Enum.reduce/3。但在这种情况下,reduce 仍然返回初始累加器值,所以这是一个很好的简化。更新了答案。
    【解决方案2】:

    Enum.reduce/3 就足够了。

    input = [%{x: 3, y: 8}, %{x: 1, y: 4}]
    
    Enum.reduce(input, %{x1: nil, x2: nil, y1: nil, y2: nil}, fn
      %{x: x, y: y}, %{x1: x1, x2: x2, y1: y1, y2: y2} ->
        %{
          x1: if(x < x1, do: x, else: x1),
          x2: if(is_nil(x2) or x > x2, do: x, else: x2),
          y1: if(y < y1, do: y, else: y1),
          y2: if(is_nil(y2) or y > y2, do: y, else: y2),
        }
    end)
    

    Erlang 中的数字(因此 Elixir)比任何其他类型更少,因此 x1y1 为零很好。对于x2y2,我们需要一个附加条件。

    【讨论】:

      【解决方案3】:

      我建议这样做,比Enum.reduce 更具可读性:

      input = [%{x: 3, y: 8}, %{x: 1, y: 4}]
      
      %{
        x1: input |> Enum.map(& &1.x) |> Enum.min(),
        x2: input |> Enum.map(& &1.x) |> Enum.max(),
        y1: input |> Enum.map(& &1.y) |> Enum.min(),
        y2: input |> Enum.map(& &1.y) |> Enum.max()
      }
      

      当然,多次迭代列表也有不利之处。根据您的具体要求,这可能是个问题。

      【讨论】:

      【解决方案4】:

      我认为Enum.reduce/3 是正确的方法,但Kernel.min/2Kernel.max/2 可以很好地处理决策逻辑(我们只需增加max 以拒绝nil

      defmodule BoundingBox do
        @moduledoc """
        Creates a bounding box around coordinates.
        """
        @initial %{x1: nil, y1: nil, x2: nil, y2: nil}
      
        def new(enumerable) do
          Enum.reduce(enumerable, @initial, &get_bounds/2)
        end
      
        defp get_bounds(%{x: x, y: y}, %{x1: left, y1: bottom, x2: right, y2: top}) do
          %{x1: min(left, x), y1: min(bottom, y), x2: max_num(right, x), y2: max_num(top, right)}
        end
      
        defp max_num(nil, b), do: b
        defp max_num(a, b), do: max(a, b)
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-10
        • 1970-01-01
        • 2015-02-11
        相关资源
        最近更新 更多