Свести список вложенных карт в Эликсире - PullRequest
0 голосов
/ 06 августа 2020

Я пытаюсь выровнять список вложенных карт, чтобы на выходе был список карт, который затем можно было вставить в таблицу базы данных. Вложенные карты могут содержать список карт. В общем, минимальный пример вложенной карты будет примерно таким:

  %{
    a: "a",
    b: "b",
    c: [
      %{
        d: "d1",
        e: [%{f: "f1", g: "g1"}],
        h: "h1",
        i: "i1"
      },
      %{
        d: "d2",
        e: [%{f: "f2", g: "g2"}],
        h: "h2",
        i: "i2"
      }
    ]
  }

Результат, который я бы искал:


  [
    %{f: "f1", g: "g1", d: "d1", h: "h1", i: "i1", b: "b", a: "a"},
    %{f: "f2", g: "g2", d: "d2", h: "h2", i: "i2", b: "b", a: "a"}
  ]
  

Длина списка равна на количество "терминальных" карт (ie. ключ f в этом примере). Также вы заметите, что там, где происходит вложение, c и e, эти ключи не нужны и поэтому отбрасываются.

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

Любая помощь или идеи о том, как подойти к этой проблеме, будут оценены.

Спасибо!

1 Ответ

1 голос
/ 06 августа 2020

Поскольку нам нужно буквально fork , проходя уровень, когда мы встречаем список как значение, нашим лучшим другом будет Task.async_stream/3. Если нам нужно выполнить это лениво, все внутренние операции также будут ленивыми, пока нам не потребуется завершить результат, чтобы вернуть его из flatten/1Enum.to_list/1.)

defmodule Flat do
  @spec flatten(map()) :: [map()]
  def flatten(input),
    do: input |> do_flatten([%{}]) |> Enum.to_list()

  @doc "here we fork it in parallel and collect results"
  defp do_flatten([%{}|_] = input, acc) do
    input
    |> Task.async_stream(&do_flatten(&1, acc))
    |> Stream.flat_map(&elem(&1, 1))
  end

  @doc """
    add `{key, value}` pairs to each list
    in currently accumulated result
  """
  defp do_flatten(%{} = input, acc) do
    Stream.flat_map(acc, fn list ->
      Enum.reduce(input, [list], &do_flatten(&1, &2))
    end)
  end

  @doc "enumerable as value → go for it"
  defp do_flatten({_k, v}, acc) when is_list(v) or is_map(v),
    do: do_flatten(v, acc)

  @doc "the leaf, add to all lists in the accumulator"
  defp do_flatten({k, v}, acc),
    do: Stream.map(acc, &Map.put(&1, k, v))
end
input = %{
  a: "a", b: "b",
  c: [
    %{d: "d1", e: [%{f: "f1", g: "g1"}], h: "h1", i: "i1"},
    %{d: "d2", e: [%{f: "f2", g: "g2"}], h: "h2", i: "i2"}]
}

Flat.flatten()
#⇒ [
#    %{a: "a", b: "b", d: "d1", f: "f1", g: "g1", h: "h1", i: "i1"},
#    %{a: "a", b: "b", d: "d2", f: "f2", g: "g2", h: "h2", i: "i2"}
#  ]

Здесь это сообщение в блоге, в котором подробно объясняется этот прием на примере загадки «Волк, Коза, Капуста» .

...