如何在elixir中转换和分组一组地图?

时间:2016-08-24 16:10:17

标签: functional-programming elixir

鉴于此数据结构:

[
  %{ collection: [ %{ description: "Foo", score: 4 }, %{ description: "Bar", score: 5 } ] },
  %{ collection: [ %{ description: "Baz", score: 3 }, %{ description: "Foo", score: 4 } ] },
  %{ collection: [ %{ description: "Bar", score: 1 }, %{ description: "Baz", score: 1 } ] }
]

我想为每个唯一description创建一组新地图,并汇总相应的分数。像这样:

[
  %{ description: "Foo", score: 8 },
  %{ description: "Bar", score: 6 },
  %{ description: "Baz", score: 4 }
]

可以将其分解为以下内容:

  1. 迭代初始数据集
  2. 为每个唯一的description
  3. 构建一组地图
  4. 使用求和函数减少集合
  5. 使用每个组的新结果构建一组地图。
  6. 我想这个过程将涉及reducemap的组合,但我所挣扎的是运行求和函数所必需的初始分组。一个惯用的灵药解决方案将是伟大的。谢谢!

3 个答案:

答案 0 :(得分:2)

我会使用Enum.flat_map/2Enum.group_by/2

list = [
  %{ collection: [ %{ description: "Foo", score: 4 }, %{ description: "Bar", score: 5 } ] },
  %{ collection: [ %{ description: "Baz", score: 3 }, %{ description: "Foo", score: 4 } ] },
  %{ collection: [ %{ description: "Bar", score: 1 }, %{ description: "Baz", score: 1 } ] }
]

list
|> Enum.flat_map(fn x -> x.collection end)
|> Enum.group_by(fn x -> x.description end)
|> Enum.map(fn {key, value} ->
    %{description: key, score: value |> Enum.map(fn x -> x.score end) |> Enum.sum}
end)
|> IO.inspect

输出:

[%{description: "Bar", score: 6}, %{description: "Baz", score: 4},
 %{description: "Foo", score: 8}]

请注意,由于group_by使用了Map,因此会保留结果列表中的项目顺序。

答案 1 :(得分:1)

这个怎么样:

defmodule Group do

  @groups [
    %{ collection: [ %{ description: "Foo", score: 4 }, %{ description: "Bar", score: 5 } ] },
    %{ collection: [ %{ description: "Baz", score: 3 }, %{ description: "Foo", score: 4 } ] },
    %{ collection: [ %{ description: "Bar", score: 1 }, %{ description: "Baz", score: 1 } ] }
  ]

  def transform(groups \\ @groups) do
    groups 
    |> Enum.reduce([], fn group, list -> list ++ group[:collection] end)
    |> Enum.group_by(&(&1.description), &(&1.score))
    |> Enum.map(fn {key, v} -> {key, Enum.sum(v)} end)
    |> Enum.into(%{})
  end
end

iex(24)> Group.transform
%{"Bar" => 6, "Baz" => 4, "Foo" => 8}

答案 2 :(得分:0)

另一种方式:

Enum.reduce(collections, %{}, fn(%{collection: list}, acc) -> 
  Map.merge(acc, Enum.reduce(list, %{}, fn(map, acc2) -> Map.put(acc2, map[:description], map) end), fn(_k, v1, v2) -> 
    Map.put(v1, :score, v1[:score] + v2[:score]) 
  end) 
end) 
|> Map.values() 

最后一行|> Map.values()对我来说是可选的。我删除了这一行,这样你就拥有了一个独特的密钥和一个对我来说更好的地图。