使用parent_id从平面列表创建分层数据结构

时间:2017-03-03 11:36:25

标签: elixir

我有一个像这样的数据结构列表:

defmodule Foo do
  defstruct [:id, :parent_id, :children]
end

lst = [
  %Foo{id: 1, parent_id: nil, children: []},
  %Foo{id: 2, parent_id: 1, children: []},
  %Foo{id: 4, parent_id: 1, children: []},
  %Foo{id: 3, parent_id: 2, children: []},
]

列表按parent_idid排序,因此列表中较低的parent_id比较低的id更早。我想将该列表转换为分层数据结构:

  %Foo{id: 1, parent_id: nil, children: [
    %Foo{id: 2, parent_id: 1, children: [
      %Foo{id: 3, parent_id: 2, children: []},
    ]},
    %Foo{id: 4, parent_id: 1, children: []}
  ]}

我对递归循环和Enum.filter有一个天真的想法,但这似乎效率很低。任何想法如何有效地解决这个问题?

修改

我似乎有一个可行的解决方案,但效率也非常低:

defp build_tree(root, []), do: root
defp build_tree(root, [node | tail]) do
  build_tree(insert_node(root, node), tail)
end

defp insert_node(root = %Message{}, node) do
  if root.message_id == node.parent_id do
    new_messages = case root.messages do
                     nil ->
                       [node]
                     _ ->
                       root.messages ++ [node]
                   end

    %Message{root | messages: new_messages}
  else
    acc = case root.messages do
            nil -> []
            _ -> root.messages
          end

    %Message{root | messages: insert_node(acc, node)}
  end
end

defp insert_node(root, node) do
  Enum.map(root, fn(x) -> insert_node(x, node) end)
end

[first | rest] = sorted_messages
tree = build_tree(first, rest)

任何有关更好解决方案的想法?

1 个答案:

答案 0 :(得分:7)

由于记录按parent_id然后id排序,因此这是一种相当有效的方法。它只涉及遍历列表两次:一次反向,一次减少。 reduce本身会执行一些Map操作,但它们仍然只有O(log n),所以整个事情都是O(n log n)

tree =
  list
  |> Enum.reverse
  |> Enum.reduce(%{}, fn foo, map ->
    foo = %{foo | children: Map.get(map, foo.id, [])}
    Map.update(map, foo.parent_id, [foo], fn foos -> [foo | foos] end)
  end)
  |> Map.get(nil)
  |> hd

我们的核心理念是保留一个由parent_id Foo键入的临时地图,每当我们看到当前Foo的id出现在地图中时,我们就会看到它children将其放入当前Foo,然后将当前Foo插入其children的{​​{1}}。最后,我们只取出parent_id Foo并使用{。}}。

演示:

parent_id: nil

输出:

defmodule Foo do
  defstruct [:id, :parent_id, :children]
end

defmodule Main do
  def main do
    list =
      [%Foo{id: 1, parent_id: nil, children: []},
       %Foo{id: 2, parent_id: 1, children: []},
       %Foo{id: 4, parent_id: 1, children: []},
       %Foo{id: 3, parent_id: 2, children: []}]

    expected =
      %Foo{id: 1, parent_id: nil, children: [
        %Foo{id: 2, parent_id: 1, children: [
          %Foo{id: 3, parent_id: 2, children: []},
        ]},
        %Foo{id: 4, parent_id: 1, children: []}
      ]}

    tree =
      list
      |> Enum.reverse
      |> Enum.reduce(%{}, fn foo, map ->
        foo = %{foo | children: Map.get(map, foo.id, [])}
        Map.update(map, foo.parent_id, [foo], fn foos -> [foo | foos] end)
      end)
      |> Map.get(nil)
      |> hd

    IO.inspect tree
    IO.inspect tree == expected
  end
end

Main.main