Learning how to use Enum.flat_map_reduce

Given:

cars =
%{
  "Ferrari" => [
    %{color: "Blue", make: "Ferrari", mileage: 120012.481},
    %{color: "Red", make: "Ferrari", mileage: 29831.021},
    %{color: "Black", make: "Ferrari", mileage: 24030.674},
    %{color: "Cobalt", make: "Ferrari", mileage: 412.811}
  ],
  "Koenigsegg" => [
    %{color: "Blue", make: "Koenigsegg", mileage: 250.762},
    %{color: "Cobalt", make: "Koenigsegg", mileage: 1297.76}, 
    %{color: "Titanium", make: "Koenigsegg", mileage: 5360.336}
  ],
  "Maserati" => [%{color: "Blue", make: "Maserati", mileage: 255.78}],
  "Mclaren" => [%{color: "Red", make: "Mclaren", mileage: 15641.469}]
}

Using Enum.flat_map_reduce the desired output is:

[
%{make: "Ferrari", color: "Blue", mileage: 120012.481},
%{make: "Ferrari", color: "Red", mileage: 29831.021},
%{make: "Ferrari", color: "Black", mileage: 24030.674},
%{make: "Ferrari", color: "Cobalt", mileage: 412.811},
%{make: "Koenigsegg", color: "Blue", mileage: 250.762},
%{make: "Koenigsegg", color: "Cobalt", mileage: 1297.76},
%{make: "Koenigsegg", color: "Titanium", mileage: 5360.336}
]

Attempt:

iex(1)> Enum.flat_map_reduce(cars, %{}, fn {k, [list]}, acc -> 
...(1)> if List.length([list] > 2) do
...(1)> Map.put(acc, {k, [list]})
...(1)> end
...(1)> end)

Error:

** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/2    
    
    The following arguments were given to :erl_eval."-inside-an-interpreted-fun-"/2:
    
        # 1
        {"Ferrari",
         [
           %{color: "Blue", make: "Ferrari", mileage: 120012.481},
           %{color: "Red", make: "Ferrari", mileage: 29831.021},
           %{color: "Black", make: "Ferrari", mileage: 24030.674},
           %{color: "Cobalt", make: "Ferrari", mileage: 412.811}
         ]}
    
        # 2
        %{}
    
    (stdlib 3.12.1) :erl_eval."-inside-an-interpreted-fun-"/2
    (stdlib 3.12.1) erl_eval.erl:829: :erl_eval.eval_fun/6
    (elixir 1.10.3) lib/enum.ex:1094: anonymous fn/3 in Enum.flat_map_reduce/3
    (elixir 1.10.3) lib/enum.ex:3686: Enumerable.List.reduce/3
    (elixir 1.10.3) lib/enum.ex:1093: Enum.flat_map_reduce/3

How shouldEnum.flat_map_reduce be implemented to obtain the desired list of maps? Thanks for all your help! :slight_smile:

You cannot have map without keys, so I assume that you want a list. In that case Enum.flat_map/2 would be enough:

Enum.flat_map(cars, fn
  {_, [_, _, _ | _] = cars} -> cars
  _ -> []
end)

Problems with Your code:

  • you are matching lists only with one element, you probably meant {k, list} instead of {k, [list]}
  • length([list]) will always return 1, you probably meant length(list)
  • length(list) > 2 can be performance hit in case of large list as it needs to traverse whole list to count the length. While match?([_, _, _ | _], list) seems arcane, it should be much faster.
  • return value of function passed to the Enum.flat_map_reduce/3 is wrong and will never work.
3 Likes

Yeah, that makes more sense :slight_smile:

The benchmark tests shared here demonstrate how Enum.reduce is far more performant than Enum.flat_map which is why I was avoiding Enum.flat_map (Or at least I think I am? lol :slight_smile: ).

In any case, how would Enum.flat_map_reduce be used here, or perhaps more appropriately, Enum.reduce?

Thanks for your insights! :slight_smile:

Do not rely on random articles from the internet, especially when you do not understand what they say. Instead:

  1. write code that works
  2. measure where is bottleneck
  3. benchmark for yourself
  4. use “performance hacks” if needed

In exactly that order.

And no, in this case the difference will be negligible if any, just because this is what your code would need to do anyway.

2 Likes

Thanks for sharing your views :slight_smile: