1

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.

4 Answers 4

5

One line using Comprehensions:

    for x <- 1..10, y <- 1..10, into: %{}, do: {{x, y}, " "}
Sign up to request clarification or add additional context in comments.

Comments

2

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} => " "}

2 Comments

Just what I needed, thanks! Btw is there a way to get rid of the parentheses in the end) bit?
Yes, you can replace both the reduce( with reduce and end) with end. I prefer writing them out when the arguments are long but they're optional.
1

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.