Comparing each element of map to the rest of the map, and update the map

As the title says, how would I compare each element of a map to the rest of the map? And then update the map in the same loop?

Consider the following:

board_position = {
	"battle_ship": {"x":5.0, "y":5.0, "alive": true},
	"destroyer_1": {"x":15.0, "y":15.0, "alive": true},
	"destroyer_2": {"x":0.0, "y":0.0, "alive": true},
	"carrier_1": {"x":15.0, "y":0.0, "alive": true},
	"carrier_2": {"x":0.0, "y":15.0, "alive": true}
}

And here’s what little code I have to show for it:

new_board_position = Enum.map(result, fn {ship_a, body_a} -> 
	Enum.map(result, fn {ship_b, body_b} ->
		if ship_a != ship_b do
			has_collided = checkShipClass(body_a, body_b) 
			# returns {"body_a": true/false, "body_b": true/false} depending if X and Y values match
			# also depends on ship class
			if has_collided == true do
				# update body_a alive
				# update body_b alive
			else
                                # body_a = body_a?
                                # body_b = body_b?
				# keep current values
			end
		end
	end)
end)

The problem I’m immediately foreseeing from my above code is that it would probably return a list, if not a list of lists - I need it to stay the same format as the initial board_position. And I’m also not sure how to update a value from within a nested .map

I’m still new to elixir, so I do apologize if this seems trivial; however, all my recent searches either result in samples that either compares two different maps, or just loops over the map without performing any updates, or doesn’t care much for the format.

Is there anything else I can try? Thanks!

Some thoughts…

It is not valid map, a map looks like this %{}, it’s tuple notation.

You could write and remove local variable.

if checkShipClass(body_a, body_b) do...

I would use a map like this to represent ship.

%{“id”: 1, “type”: “destroyer”, “x”…}

I might even prefer to use a struct for this, and use atom keys instead.

As seen, Enum.map will transform into a list. Maybe Enum.reduce, or a list comprehension (for) would do a better job.

1 Like

For example, with customized data…

m = %{
  1 => %{alive: true, id: 1, type: "battle_ship", x: 5.0, y: 5.0},
  2 => %{alive: true, id: 2, type: "destroyer", x: 15.0, y: 15.0},
  3 => %{alive: true, id: 3, type: "destroyer", x: 0.0, y: 0.0},
  4 => %{alive: true, id: 4, type: "carrier", x: 15.0, y: 0.0},
  5 => %{alive: true, id: 5, type: "carrier", x: 5.0, y: 5.0}
}
# collisions per ship
Enum.reduce(m, %{}, fn 
  {id, ship}, acc -> 
    collisions = Enum.filter(m, fn {id, sh} -> 
      sh.alive && id != ship.id && sh.x == ship.x && sh.y == ship.y 
    end) 
    Map.put(acc, id, collisions) 
end)
%{
  1 => [{5, %{alive: true, id: 5, type: "carrier", x: 5.0, y: 5.0}}],
  2 => [],
  3 => [],
  4 => [],
  5 => [{1, %{alive: true, id: 1, type: "battle_ship", x: 5.0, y: 5.0}}]
}

As an advice, don’t try to make too much in only one function. You can split into smaller functions, and chain them with pipe.

I apologize for not getting back to you earlier, the lack of % in the initial board_position was a typo, as is the : where => should be, sorry; it’s loaded from an external JSON file from some other function I’m not privy to, so I can’t change it myself, nor can I change the output format much. I should have worded the question better.

FP is all about transforming data.

You might not have the possibility to change the output of the api, but You can reshape it to suit your needs.

You can create small functions to do some partial transformation, and compose them to build a transformation pipeline.

One could change the string keys to atom, another could extract id from a string (carrier_1) etc.

I’ve taken a crack at it with some modified version of the code you posted (thanks for that, btw!):

collision_result = Enum.reduce(result, fn {ship_a, body_a}, acc -> 
	new_result = Enum.map(result, fn {ship_b, body_b} ->
		if (ship_a != ship_b) do
			has_collided = checkStaticCollisions(body_a, body_b, const_physics["billiard_ball_radius"])
            # Takes in parameters (Body A, Body B, radius)
            # Returns true or false
			if has_collided == true do
				calc = PGS.Collision.calculatePosAfterCollision(ship_a, body_a, ship_b, body_b, const_physics["billiard_ball_radius"])
                # Takes in parameters (Key of Ball A, Value of Ball A, Key of Ball B, Value of Ball B, radius)
                # Returns {"id_of_ball_a" => {new X and Z pos}, "id_of_ball_b" => {new X and Z pos}}
				new_a_pos = calc[ship_a]
				new_b_pos = calc[ship_b]
				new_a = %{"x" => new_a_pos["x"], "y" => new_a_pos["y"], "velocity_x" => body_a["velocity_x"], "velocity_y" => body_a["velocity_y"], "alive" => body_a["alive"]}
				new_b = %{"x" => new_b_pos["x"], "y" => new_b_pos["y"], "velocity_x" => body_b["velocity_x"], "velocity_y" => body_b["velocity_y"], "alive" => body_b["alive"]}
				Map.put(acc, ship_a, new_a)
				Map.put(acc, ship_a, new_a)
			else
				acc
			end
		else
			acc
		end
	end)
end)

This doesn’t work, however, not the least bit because it returns a list of list. I’m also pretty sure the returns are wrong.

Frankly its hard to understand your code, because its so deeply nested and its rather Java than Elixir. (I think thats normal when coming from an imperative/OOP language, I did the same, even subclassed a GenServer once).
Try to think about your data structures, how they have to look, so that they are easily transforming the way you want.
Try to build upon the Gorillas example. See how nicely he gets what he wants, with just putting one filter-result into the accumulator?

Hello, yes sorry for that, it’s a bit OOP-ish, it really took me out due to the lack of a global variable, which was how it was done in C#.

Building off Gorillas example got me this far:

collision_result = Enum.reduce(result, %{}, fn {key, val}, acc ->
	collision = Enum.filter(result, fn {inner_key, inner_val} ->
		checkStaticCollisions(val, inner_val, const_ship_size)
	end)
	Map.put(acc, key, collision)
end)

The checkStaticCollisions returns a true or false depending on the whether or not the bodies passed (val and inner val in this case) have collided. I assume if they do, the enum.filter returns the inner_key and inner_val.

The problem is I need to update it then and there when it collides; for cases like, say, if battle_ship colliding with destroyer_1 also causes destroyer_1 to collide with destroyer_2.

In that case, I would need to update both battle_ship and destroyer_1 location in one pass / loop, and at the next one, seeing that, destroyer_1's new location collides with destroyer_2, I would need to update both of those as well. It’s the “update both” part that’s throwing me off the most.

I read a bit about filter here, it says it’s not capable of transforming an element at the same time, and it pointed me to flat_map, which I am currently checking out, but isn’t that for flattening results? Going from that got me thinking about something like this:

collision_result = Enum.reduce(result, %{}, fn {key, val}, acc ->
	collision = Enum.flat_map(result, fn {inner_key, inner_val} ->
		has_collided = checkStaticCollisions(val, inner_val, const_ship_size)
		case has_collided do
		# Not sure if below is possible:
		new_loc = calculatePosAfterCollision(key, val, inner_key, inner_val, const_ship_size)
		# Returns {"key" => %{new val}, "inner_key" => %{new inner_val}}
		{new_loc[key], new_loc[inner_key]} -> []
		# Not sure what to put there either or what to return when they don't collide
	end)
	Map.put(acc, key, collision)
end)

Start to write the input, and the expected output. Then build a pipeline to achieve this transformation.

You might not believe it, but I try NOT to use if, because You can make it differently.

collision will be true or false

No, it is not possible

Update in immutable world is different, update in place also… (this is how You are used to, but it’s different in FP)

Of course it does not transform an element, it transforms the list. If You want to transform, You should use Enum.map or Enum.reduce.

1 Like

This is pseudo code of what I would do.

m = reshape(data)

collisions = for {x_id, x} <- m, {y_id, y} <- m, x_id != y_id && check_static_collision(x, y), do: [x, y]
new_m = Enum.reduce(collisions, m, &apply_collision(&1, &2))

# repeat calculate & apply collisions for new_m, until collisions is empty
# which could be done by recursion

Cross product with for is a powerful tool…
Recursion is a good tool, not as in other languages, where You might be told not to use it.

Here’s my take on it using comprehensions:

#! /usr/bin/elixir

defmodule Ships do
  def update(board) do
    for {ship_a, body_a} <- board,
        {ship_b, body_b} <- board,
        ship_a != ship_b and collision?(body_a, body_b),
        reduce: board do
      acc -> put_in(acc, [ship_a, "alive"], false)
    end
  end

  defp collision?(body_a, body_b) do
    # TODO implement your collision check
    body_a["x"] == body_b["x"]
  end
end

board = %{
  "battle_ship" => %{"x" => 5.0, "y" => 5.0, "alive" => true},
  "destroyer_1" => %{"x" => 15.0, "y" => 15.0, "alive" => true},
  "destroyer_2" => %{"x" => 0.0, "y" => 0.0, "alive" => true},
  "carrier_1" => %{"x" => 15.0, "y" => 0.0, "alive" => true},
  "carrier_2" => %{"x" => 0.0, "y" => 15.0, "alive" => true}
}

board = Ships.update(board)

IO.inspect(board)

Result:

%{
  "battle_ship" => %{"alive" => true, "x" => 5.0, "y" => 5.0},
  "carrier_1" => %{"alive" => false, "x" => 15.0, "y" => 0.0},
  "carrier_2" => %{"alive" => false, "x" => 0.0, "y" => 15.0},
  "destroyer_1" => %{"alive" => false, "x" => 15.0, "y" => 15.0},
  "destroyer_2" => %{"alive" => false, "x" => 0.0, "y" => 0.0}
}

Not sure if this is your desired outcome. Also note that we are checking too many combinations (e.g. “battle_ship” → “destroyer_1” and then again “destroyer_1” → “battle_ship”), which doesn’t matter for such a small map but does for larger inputs.

This would be my pseudo code with a module…

defmodule Demo do
  def reshape(data) do
    data
  end

  def process(m) do
    process(m, calculate_collisions(m))
  end

  defp process(m, []), do: m
  defp process(m, collisions) do
    m = Enum.reduce(collisions, m, &apply_collision/2)
    process(m, calculate_collisions(m))
  end

  # Here You would probably want to exclude duplicates before checking collision!
  defp calculate_collisions(m) do
    for {x_id, x} <- m, {y_id, y} <- m, x_id != y_id && check_static_collision(x, y), do: [x, y]
    |> Enum.uniq_by(& Enum.sort(&1))
  end

  defp apply_collision(_collision, m) do
    m  # here You would return a modified m, after applying collision
  end

  defp check_static_collision(_x, _y), do: true
end

The code is not implemented, but it would look like this… a bunch of small functions doing small thing right.

The reshape is to sanitize data from outside world before it enters your Core domain.

There is no if, and functions are small.

1 Like

I’m going to suggest “you are doing it wrong”… One of the nice suggestions I’ve seen from several elixir advocats is to split the computation from the side effects. So I think you might want to consider re-architecting this to a new function which say “computes_collisions” and a second function which applies those collisions back to the data structure.

This may turn out to be useful in the future as you can take the output from the first function and check if it’s non empty, this may in turn trigger further actions you want to do and applying those changes to the data structure may turn out to be a relatively minor part of this? In any case from Elixir’s point of view it will be a two stage operation, so you haven’t made it any less efficient. (immutable variables… Sorry…)