【发布时间】: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
给定一个点列表
[
%{ x: 3, y: 8 },
...,
%{ x: 1, y: 4 }
]
计算包含所有点的框坐标的最佳方法是什么 - 即边界:%{ x1: 1, y1: 4, x2: 3, y2: 8 }
我有一种预感,我可以使用 Enum.flat_map_reduce/3,但目前语法让我感到困惑。
【问题讨论】:
标签: functional-programming elixir
我实际上在去年 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
【讨论】:
Enum.reduce/3。但在这种情况下,reduce 仍然返回初始累加器值,所以这是一个很好的简化。更新了答案。
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)比任何其他类型更少,因此 x1 和 y1 为零很好。对于x2 和y2,我们需要一个附加条件。
【讨论】:
我建议这样做,比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()
}
当然,多次迭代列表也有不利之处。根据您的具体要求,这可能是个问题。
【讨论】:
Enum.min_by/3 和 Enum.max_by/3 至少不会产生 2 个中间冗余列表。
我认为Enum.reduce/3 是正确的方法,但Kernel.min/2 和Kernel.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
【讨论】: