使用Enum.group_by对地图项子字符串进行分组

时间:2019-02-20 14:49:41

标签: elixir

我有一张看起来像的地图:

z = %{
   "dd_1_a" => 1,
   "dd_1_b" => 2,
   "dd_1_c" => 3,
   "dd_2_a" => 4,
   "dd_2_b" => 5,
   "dd_2_c" => 6
}

我正在尝试将其转换为以下形式的列表:

[
{a: 1, b: 2, c: 3},
{a: 4, b: 5, c: 6}
]

我已经了解到了

z 
|> Map.new(fn {k, v} -> {tl(String.split(k, "_")), v} end ) 
|> Enum.group_by(fn {k, v} -> hd(k) end )

哪个给:

%{
  "1" => [{["1", "a"], 1}, {["1", "b"], 2}, {["1", "c"], 3}],
  "2" => [{["2", "a"], 4}, {["2", "b"], 5}, {["2", "c"], 6}]
}

我只是缺少下一步,那就是将值转换为关键字列表。

4 个答案:

答案 0 :(得分:1)

在这里使用group_by

z
|> Enum.group_by(&String.at(elem(&1, 0), 3), &{String.to_atom(String.at(elem(&1, 0), 5)), elem(&1, 1)})
|> Map.values
|> Enum.map(&Map.new(&1))

答案 1 :(得分:0)

我假设1中的dd_1_a意味着它应该在dd_2_a之前。在这种情况下,您需要在某些时候进行排序,因为不能保证枚举地图时的键顺序。

# ascending sort function
ascending = fn {k1, _}, {k2, _} -> k1 <= k2 end

%{
  "dd_1_a" => 1,
  "dd_1_c" => 3,
  "dd_20_a" => 4,
  "dd_1_b" => 2,
  "dd_20_b" => 5,
  "dd_20_c" => 6
}
# Parse the keys
|> Enum.map(fn {k, v} -> {Regex.run(~r/dd_(\d+)_(\w+)/, k), v} end)

# Convert into groups of 1 => [a: 1, b: 2, ...]
|> Enum.group_by(
  fn {[_, k, _v1], _v2} -> String.to_integer(k) end,
  fn {[_, _k, v1], v2} -> {String.to_atom(v1), v2} end
)

# Sort the keys, because the order of the keys in maps is not guaranteed
|> Enum.sort(ascending)

# Sort the internal lists too, because they were extracted from unsorted keys
|> Enum.map(fn {_, vals} -> Enum.sort(vals, ascending) end)

# Convert to tuples (skip this if you want keyword lists)
|> Enum.map(&List.to_tuple/1)

输出:

[{{:a, 1}, {:b, 2}, {:c, 3}}, {{:a, 4}, {:b, 5}, {:c, 6}}]

您提到您想要关键字列表,因此,如果跳过最后一步,输出为:

[[a: 1, b: 2, c: 3], [a: 4, b: 5, c: 6]]

答案 2 :(得分:0)

我回来了。 :-)

由于我在其他答案中提到的排序问题,我认为这不能解决问题,但确实可以回答您的问题:

%{
  "1" => [{["1", "a"], 1}, {["1", "b"], 2}, {["1", "c"], 3}],
  "2" => [{["2", "a"], 4}, {["2", "b"], 5}, {["2", "c"], 6}]
}
|> Enum.sort(fn {k1, _}, {k2, _} -> String.to_integer(k1) <= String.to_integer(k2) end)
|> Enum.map(fn {_, v} -> v end)
|> Enum.map(fn group -> Enum.map(group, fn {[_, k], v} -> {String.to_atom(k), v} end) end)
|> Enum.map(&List.to_tuple/1)

输出:

[{{:a, 1}, {:b, 2}, {:c, 3}}, {{:a, 4}, {:b, 5}, {:c, 6}}]

答案 3 :(得分:0)

您也可以尝试使用dd_ [index] _key来按索引顺序对元素进行排序

评论解释了一些代码


z = %{
  "dd_1_a" => 1,
  "dd_1_b" => 2,
  "dd_1_c" => 3,
  "dd_3_a" => 7, # indexes out of whack here
  "dd_4_b" => 10, # indexes out of whack here
  "dd_4_a" => 9, # indexes out of whack here
  "dd_3_c" => 8, # indexes out of whack here
  "dd_2_a" => 4,
  "dd_2_b" => 5,
  "dd_2_c" => 6
}

z
|> Enum.map(fn {"dd_" <> k1,v} -> # use pattern-matching to get the index and key
  # can't use pattern-matching for this, because 
  # patterns need to have deterministic length
  # so use String.split
  [i, k] = String.split(k1, "_") 
  {i, k, v}
end) \
|> Enum.group_by(fn {i, _, _} -> i end)  # group by the index
|> Enum.map(fn {i, arr} ->
  # convert values of each index to an array
  {i, arr |> Enum.map(fn {_i, k, v} -> {k |> String.to_atom, v} end)}
end) 
|> Enum.sort(fn {i1, _}, {i2, _} -> i1 < i2 end)  # sort by index
|> Enum.map(fn {_i, v} -> v |> List.to_tuple end) # remove the index, only keep key-values

output = [
  {{:a, 1}, {:b, 2}, {:c, 3}},
  {{:a, 4}, {:b, 5}, {:c, 6}},
  {{:a, 7}, {:c, 8}},
  {{:a, 9}, {:b, 10}}
]