Manipulating maps

Hello,

I’m quite new to Elixir, and I’m having issues manipulating maps and structs. For example, given the following map:

  def selected_products do
    [
      %{id: "1", name: "Product 0", group: "0", price: 9.99},
      %{id: "10", name: "Product 1", group: "1", price: 4.99},
      %{id: "15", name: "Product 2", group: "1", price: 4.99},
      %{id: "333", name: "Product 3", group: "3", price: 14.99}
    ]
  end

Why this works fine:

def total do

__MODULE__.selected_products()

|> Enum.map(& &1[:price])

|> Enum.sum()

end

But this throws an error:

def subtotal1 do

for product = %{ group: group } <- selected_products(), group == "1", do: product

|> Enum.map(& &1[:price])

|> Enum.sum()

end

The for loop above returns a map with just the two records in group 1. I’ve tested it.

Thanks a lot in advance

Because you are limiting what the for statement works with. The second clause – group == "1" – does it.

Also, how can it throw an error if it returns something that you don’t expect? Throwing an error wouldn’t show you any return value.

1 Like

It’s easier if you post the actual error you see when you say one is thrown…

My current assumption is, that it’s erroring because enumerable is not implemented for tuples.

The compiler things the pipes in your list comprehension are part of the do:.

Use the explicit long form for the do ... end and it should work as expected.

Ps I’m on a mobile therefore I was unable to check if my assumption is close enough, but I do not want to loose the thought.

1 Like

Bingo!

Using https://elixirformatter.com/

selected_products = [
  %{id: "1", name: "Product 0", group: "0", price: 9.99},
  %{id: "10", name: "Product 1", group: "1", price: 4.99},
  %{id: "15", name: "Product 2", group: "1", price: 4.99},
  %{id: "333", name: "Product 3", group: "3", price: 14.99}
]

f = fn p ->
  for product = %{group: group} <- p,
      group == "1",
      do:
        product
        |> Enum.map(& &1[:price])
        |> Enum.sum()
end

f.(selected_products)
iex(1)> selected_products = [
...(1)>   %{id: "1", name: "Product 0", group: "0", price: 9.99},
...(1)>   %{id: "10", name: "Product 1", group: "1", price: 4.99},
...(1)>   %{id: "15", name: "Product 2", group: "1", price: 4.99},
...(1)>   %{id: "333", name: "Product 3", group: "3", price: 14.99}
...(1)> ]
[
  %{group: "0", id: "1", name: "Product 0", price: 9.99},
  %{group: "1", id: "10", name: "Product 1", price: 4.99},
  %{group: "1", id: "15", name: "Product 2", price: 4.99},
  %{group: "3", id: "333", name: "Product 3", price: 14.99}
]
iex(2)> 
nil
iex(3)> f = fn p ->
...(3)>   for product = %{group: group} <- p,
...(3)>       group == "1",
...(3)>       do:
...(3)>         product
...(3)>         |> Enum.map(& &1[:price])
...(3)>         |> Enum.sum()
...(3)> end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex(4)> 
nil
iex(5)> f.(selected_products)
** (FunctionClauseError) no function clause matching in Access.get/3    
    
    The following arguments were given to Access.get/3:
    
        # 1
        {:group, "1"}
    
        # 2
        :price
    
        # 3
        nil
    
    Attempted function clauses (showing 5 out of 5):
    
        def get(%module{} = container, key, default)
        def get(map, key, default) when is_map(map)
        def get(list, key, default) when is_list(list) and is_atom(key)
        def get(list, key, _default) when is_list(list)
        def get(nil, _key, default)
    
    (elixir) lib/access.ex:316: Access.get/3
    (elixir) lib/enum.ex:1318: anonymous fn/3 in Enum.map/2
    (stdlib) maps.erl:257: :maps.fold_1/3
    (elixir) lib/enum.ex:1941: Enum.map/2
iex(5)> 

Add parenthesis

selected_products = [
  %{id: "1", name: "Product 0", group: "0", price: 9.99},
  %{id: "10", name: "Product 1", group: "1", price: 4.99},
  %{id: "15", name: "Product 2", group: "1", price: 4.99},
  %{id: "333", name: "Product 3", group: "3", price: 14.99}
]

f = fn p ->
  for(product = %{group: group} <- p, group == "1", do: product)
  |> Enum.map(& &1[:price])
  |> Enum.sum()
end

f.(selected_products)
iex(1)> selected_products = [
...(1)>   %{id: "1", name: "Product 0", group: "0", price: 9.99},
...(1)>   %{id: "10", name: "Product 1", group: "1", price: 4.99},
...(1)>   %{id: "15", name: "Product 2", group: "1", price: 4.99},
...(1)>   %{id: "333", name: "Product 3", group: "3", price: 14.99}
...(1)> ]
[
  %{group: "0", id: "1", name: "Product 0", price: 9.99},
  %{group: "1", id: "10", name: "Product 1", price: 4.99},
  %{group: "1", id: "15", name: "Product 2", price: 4.99},
  %{group: "3", id: "333", name: "Product 3", price: 14.99}
]
iex(2)> 
nil
iex(3)> f = fn p ->
...(3)>   for(product = %{group: group} <- p, group == "1", do: product)
...(3)>   |> Enum.map(& &1[:price])
...(3)>   |> Enum.sum()
...(3)> end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex(4)> 
nil
iex(5)> f.(selected_products)
9.98
iex(6)> 

You guys are awesome!!! Yes, I was about to respond that NobbZ nailed it.

Thank you all