Reputation: 4231
I am creating a 2d map and want to start by pre-filling it with empty values.
I know the following will not work in Elixir, but this is what I am trying to do.
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
Then I will be drawing shapes onto that map like
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
I have tried doing it as a 2D array, but I think flat map with coordinate touples as keys will be easier to work with.
I know I am supposed to use recursions, but I don't really have a good idea of how to do it elegantly and this scenario keeps coming up and I haven't seen a simple/universal way to do this.
Upvotes: 1
Views: 2522
Reputation: 13
I've tried to create my own solution, that would do the above in a manner that is understandable but still as concise as possible (I'm just a beginner myself).
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
First of all, since a Range
is inclusive (i.e. 1..4 is [1,2,3,4] and not [1,2,3]), I use actual bounds instead of width and height parameters. This makes it more clear to me what I have to do with the code.
Next, I make a simple comprehension, as shown in the answer of @AA., basically looping over all possible combinations of x and y.
If you return a two-element tuple within the body of that loop, then the first element of the tuple will be the key and the second will be the value inserted into the map.
Within the body, I use draw_tile
, a function which takes coordinates and a boolean indicating whether the tile is a border-tile or not.
Finally, the function border?
just checks if either x
or y
are equal to left_bound
or right_bound
and lower_bound
or upper_bound
respectively.
In Elixir, although if
statements exist, it is more idiomatic to use pattern matching in a function, as I did with draw_tile
.
Also, I think (personal opinion, not sure if this is reflected in the Elixir Community as a whole), nesting should be avoided as much as possible.
Edit: You will also find a lot of information on syntax in the Elixir docs.
For example, see Kernel.SpecialForms.for/1 or Kernel.in/2
Upvotes: 0
Reputation: 4606
One line using Comprehensions:
for x <- 1..10, y <- 1..10, into: %{}, do: {{x, y}, " "}
Upvotes: 5
Reputation: 4507
Another approach is to create a list tuples with comprehensions and convert it into a map.
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)>
Upvotes: 1
Reputation: 222118
You can use two nested Enum.reduce/3
here, passing the map as the accumulator, instead of writing recursive functions yourself:
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)
Output:
%{{1, 1} => " ", {1, 2} => " ", {1, 3} => " ", {1, 4} => " ", {2, 1} => " ",
{2, 2} => " ", {2, 3} => " ", {2, 4} => " ", {3, 1} => " ", {3, 2} => " ",
{3, 3} => " ", {3, 4} => " "}
Upvotes: 2