Enum.map inside a fn of other Enum.map

I have the following code (where I’m trying to call enum.map inside a function of other Enump.map)

a= Map.get(b, "a")
      i = Map.get(b, "i")
      |> Enum.map(fn %{"ban" => ban} = node ->
        key=""
        x= ""
        y= ""
        z= ""
        key=d <>"-"<> a["bdl"] <>"-"<> "ban" <>"-"<> to_string(ban["w"]) <>"-"<> to_string(ban["h"])
        tag = get(key)
        if tag != nil do
          success = true
          x= tag["x"]
          y= tag["y"]
          z= tag["z"]      
          Map.put(node,"t", z)
          
          if y!= "" && Map.has_key?(a, "id") do
            a|> Enum.map(fn %{"id" => id} = node2 ->
              Map.put(node2,"id",y)
            end)
          end #End y!= "" && Map.has_key?(a, "id")
        
        end # End if tag
      end) # End i= Map.get(b, "i")

but I get the error in the following line

a|> Enum.map(fn %{"id" => id} = node2 ->

the error

# no function clause matching in anonymous fn/1 in

How could I solve this issue?

Enum.map takes an Enumerable and a function, and maps each item in the enumerable to the result of calling the given function on it. Look close at your code here:

if y != "" && Map.has_key?(a, "id") do
  a |> Enum.map(fn %{"id" => id} = node2 ->
    Map.put(node2,"id",y)
  end)
end

You verify if the map a has the key "id", so a is a map. When you use Enum.map on a map, the elements being iterated are {key, value} tuples, so they definitely won’t match %{"id" => id}, hence the error.

My recommendation is to first unnest your code and split it into smaller functions. This will make it a lot easier to follow, test, and debug.

1 Like

I have tried to change it back to a map like this

 a= Enum.into(a, %{})
            a|> Enum.map(fn %{"id" => id} = node2 ->
              Map.put(node2,"id",y)
            end)

but it didn’t work either

This will still iterate over a list of key-value-pairs.

Calling any function from Enum is semantically equivalent to calling enumerable |> Enum.into([]) |> your_function_call.

And as any map (the datastructuire) can only have a single "id" key, you do not need to Enum.map over the map, but you can instead use Map.update(!) to change the value.

2 Likes

Sorry I’m still new to elixir, I have tried the following:

if y != "" && Map.has_key?(a, "id") do
    Map.update(a,"id",y)
end

but still got the error

no function clause matching in Map.update!/3

Well, Map.update/4 (or Map.update!/3) does not work that way: it expects an update function that receives the value and computes the new value:

a = %{"x" => 1}

# Say we want to increment the value associated to "x" by 1:
Map.update(a, "x", 0, fn value ->
  value + 1
end)

Also, I suspect you might be misunderstanding how Elixir data structures work. Functions like Map.put(...) or Map.update(...) return a modified copy of the original map. They do not mutate the original map. This is a very important core concept about Elixir: all data structures are immutable.

So, for example:

a = %{ "one" => 1, "two" => 2 }

# This returns a new map, with the new "three" key set to 3
Map.put(a, "three", 3)
#=> %{ "one" => 1, "two" => 2, "three" => 3 }

# But the old map is still unchanged:
a
#=> %{ "one" => 1, "two" => 2 }

I suggest you take your time to understand how to work with immutable data structures, everything will be much clearer afterwards.

4 Likes

I took a stab at making this a little more idiomatic. I found it a bit difficult to follow the logic but structurally you can remove a lot of the code by stronger pattern matching and by decomposing into functions with multiple heads. Here is my quick attempt to demonstrate:

  def transform(%{"a" => %{"bdl" => bdl} = a, "i" => i} = b) do
    Enum.map(i, fn %{"ban" => %{"w" => w, "h" => h}} = node ->
      key = "#{d}-#{bdl}-ban-#{w}-#{h}"
      if tag = get(key) do
        transform_node(node, tag, a)
      else
        node
      end
    end)
  end
  
  # "y" is ""
  def transform_node(node, %{"y" => "", "z" => z} = tag, _a) do
    Map.put(node, "t", z)
  end
  
  # Since you are checking if a has the key "id" then there can only be one
  # "id" key and therefore Enum isn't useful - just update
  def transform_node(node, %{"x" => x, "y" => y, "z" => z} = tag, %{"id" => id} = a) do
    # Logic not clear to me what really happens here
  end
  
  # a has no key "id"
  def transform_node(node, %{"z" => z}, _a) do
    Map.put(node, "t", z)
  end
2 Likes