Использование Enum.group_by для группировки по ключевой подстроке карты - PullRequest
0 голосов
/ 20 февраля 2019

У меня есть карта, которая выглядит следующим образом:

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 голосов
/ 21 февраля 2019

Вы также можете попробовать это, он сортирует элементы в порядке индекса, предполагая, что 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}}
]
0 голосов
/ 20 февраля 2019

Я предполагаю, что 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]]
0 голосов
/ 20 февраля 2019

Я вернулся.: -)

Я думаю, что это не решает проблему из-за проблем с сортировкой, которые я упоминал в моем другом ответе, но он отвечает на ваш вопрос:

%{
  "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}}]
0 голосов
/ 20 февраля 2019

Вот с использованием 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))
...