Сопоставьте группу по нескольким значениям в Elixir

#elixir

#elixir

Вопрос:

Итак, у меня есть эта карта

     map = [
             %{id: 2, brand: "TUTU", reference: "1234"}, 
             %{id: 2, brand: "TUTU", reference: "4567"}, 
             %{id: 3, brand: "TOTO", reference: "789456"}
    ]
  

И я хотел бы сгруппировать ее по идентификатору, чтобы получить что-то вроде этого :

     [
        %{
            id: 2,
            brand: "TUTU",
            reference: [
                 "1234",
                 "5845"
              ]
        },
        %{
             id: 3,
             brand: "TOTO",
             reference: [
                  "4587"
             ]
        }
    ]
  

Я пытался использовать Enum.group_by так

 map
|> Enum.group_by(fn entry -> entry.brand end)
  

Но результат выглядел так :

     %{
      "TOTO" => [%{brand: "TOTO", id: 3, reference: "789456"}],
      "TUTU" => [
        %{brand: "TUTU", id: 2, reference: "1234"},
        %{brand: "TUTU", id: 2, reference: "4567"}
      ]
    }
  

Я чувствую, что я близок к решению, но я не знаю, как я могу перегруппировать свою информацию в списки вместо того, чтобы использовать ключи в качестве индексов….

Извините, я не очень понимаю, я просто не знаю, как описать мою проблему

Ответ №1:

Другой возможностью было бы Enum.reduce/3 напрямую перейти к желаемой структуре с Kernel.update_in/3 :

 data
|> Enum.reduce(%{}, fn %{id: id, brand: brand, reference: ref}, acc ->  
  update_in(acc, [{id, brand}], fn
    nil -> %{id: id, brand: brand, reference: [ref]}
    refs -> %{refs | reference: [ref | refs.reference]}
  end)
end)
|> Map.values()
  

Ответ №2:

Если вам не нужно быть очень изобретательным в обнаружении ключей, которые не нужно группировать, и вы можете просто перечислить их заранее, то вложенный Enum.map , подобный этому, будет делать то, что вы хотите:

 data = [
  %{id: 2, brand: "TUTU", reference: "1234"},
  %{id: 2, brand: "TUTU", reference: "4567"},
  %{id: 2, brand: "TOTO", reference: "4567"},
  %{id: 3, brand: "TOTO", reference: "789456"}
]

keys_to_group_by = [:id, :brand]
keys_to_list = [:reference]

data
|> Enum.group_by(amp;Map.take(amp;1, keys_to_group_by))
|> Enum.map(fn {key, values} ->
  keys_to_list
  |> Enum.map(fn key_to_list ->
    {key_to_list, Enum.map(values, amp; amp;1[key_to_list])}
  end)
  |> Enum.into(key)
end)
  

Обратите |> Enum.group_by(amp;Map.take(amp;1, keys_to_group_by)) внимание — это часть, которая группируется по многим ключам, так что %{id: 2, brand: "TOTO"} и %{id: 2, brand: "TUTU"} попадают в разные сегменты.

Ответ №3:

Вы также можете взглянуть на Enum.reduce/3 и Map.update/4 вместо простого Enum.group_by/2 , чтобы избавить вас от необходимости затем сопоставлять сгруппированные записи только со ссылками.

 map
|> Enum.reduce(%{}, amp;group_by_brand/2)
|> Enum.map(amp;to_map/1)

...

defp group_by_brand(entry, acc) do
  Map.update(acc, {entry.id, entry.brand}, [entry.reference], amp;[entry.reference | amp;1])
end

defp to_map({{id, brand}, references}) do
  %{id: id, brand: brand, references: references}
end