Given a list of points
[
%{ x: 3, y: 8 },
...,
%{ x: 1, y: 4 }
]
What is the best way to calculate the coordinates of a box that will contain all points - ie the bounds: %{ x1: 1, y1: 4, x2: 3, y2: 8 }
I have a hunch I can use Enum.flat_map_reduce/3
but the syntax is baffling me at the moment.
Enum.reduce/3
would be enough.
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)
A number in Erlang (and hence Elixir ) is less than any other type, hence nils for x1
and y1
are simply fine. For x2
and y2
we require an additional condition.
I actually ran into this as part of Advent of Code 2018 day 10 last year. This was my method . It sets the bounding box to the initial point, and then expands it as it finds points further out. No need for nil
. :-)
Here it is adapted for your data:
# 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
I would propose this, as more readable than an 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()
}
Of course there is the downside of iterating over the list multiple times. Depending on your exact requirements, that might be a problem.
I think Enum.reduce/3
is the right way to go, but Kernel.min/2
and Kernel.max/2
can handle the decision logic pretty well (we just have to augment max
to reject 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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.