Update value in map based on Enum.member? check

Ultimately I’d like to update the values of keys in myMap based on whether the key is in myList and in myOtherList:

myList = ["a", "b", "c"]
myOtherList = ["b"]
myMap = %{"a" => "no", "b" => "no", "c" => "no"}

Enum.each(myList, fn e -> 
    case Enum.member?(myOtherList, e) do
        true -> Map.replace(myMap, e, "yes")
        false -> Map.replace(myMap, e, "no")
    end
end)

myMap doesn’t update.

I believe the problem would be because of immutability, but I still cannot arrive to a solution. I’m open to other Map function suggestions.

1 Like

I suggest using if else for this. Also, to mutate your map you have to use explicit assignment, so my_map = if (...).

Oh, and in Elixir snake_case is the most widely used convention.

1 Like

Check out Enum.reduce

1 Like
defmodule Demo do

  def replace_with(list, value) do
    fn {x , _} = pair ->
      cond do
        Enum.member?(list, x) -> {x, value}
        true -> pair
      end
    end
  end

end

myOtherList = ["b"]
myMap = %{"a" => "no", "b" => "no", "c" => "no"}

newMap =
  myMap
  |> Enum.map(Demo.replace_with(myOtherList, "yes"))
  |> Map.new()

IO.inspect(newMap)
$ elixir demo.exs
%{"a" => "no", "b" => "yes", "c" => "no"}
$
1 Like

Will myList always be identical to Map.keys(myMap)? Or will they diverge?

Will Map.values(myMap) always be identical to a list of "no"? Or might there be other values?

In the following solution, I will assume, that myList and Map.keys(myMap) might be different, while the values all have to be "no".

intersection_of_keys = myList -- myOtherList

for {k, v} <- myMap, into: %{} do
  if k in intersection_of_keys, do: {k, "yes"}, else: {k, "no"}
end

The reason why your original code did not work is, because the inner binding in the fn you pass to Enum.each/2 does not change the outer binding of myMap, always remember, elixir is immutable, it does only support shadowing, but shadowing does never leak its scope.

1 Like

To me it seems peculiar that the original code completely ignores all the values that are already in the Map - what is the point of having a Map then?

myOtherList = ["b"]
myMap = %{"a" => "no", "b" => "no", "c" => "no"}
difference = Map.keys(myMap) -- myOtherList
new_value = "yes"

newMap =
  (for {k, _} = pair <- myMap, do:
    if k in difference, do: pair, else: {k, new_value})
  |> Map.new()

IO.inspect(newMap)

Let me be more specific by redefining the problem:

For each key in map_A (we don’t care about the values) get another known map (for example sake: map_B) which is referenced by the key at map_A and test whether any of the keys in map_B matches some variable x. If match, update value at key in map_A to "yes", else "no". And this is the problem.

The problem exists, as NobbZ said:

and I assume in this case that any iterator will not allow this. That goes for Enum.each/2 and:

for x <- some_enumerable do

end

I cannot see further than this:

x = "something"
for f <- Map.keys(map_A) do
    if(Enum.member?(Map.keys(map_B), x) == true) do
        Map.replace(map_A, f, "yes")
    else
        Map.replace(map_A, f, "no")
    end
end

There is no “iterator”. for is a comprehension. Simply speaking it consumes an enumerable and produces a list (or whatever else :into identifies) - it’s an expression like everything else in Elixir, not a statement.

1 Like

The solution was solved in another topic:

Stop thinking of “variables”- all values in Elixir are immutable. However identifiers can be rebound to new values. So the only thing that can “vary” is the value the identifier is bound to - not the value itself.
Imperative languages are about PLace-Oriented Programming (PLOP) - in functional programming you are programming with values (The Value of Values).

Furthermore you don’t “loop” in Elixir - you recurse.

2 Likes

You are right. As much as I have learnt and understood this concept, I should be more specific with my language. ty