简体   繁体   中英

How to convert algorithm to functional programming?

I'm totally new to functional programming and elixir, and even after learning about the syntax, I'm having a hard time wrapping my head around solving issues with immutability.

Consider the following simple algorithm I wrote in python. It receives a list of levels of a tree. Each level is a list of nodes where a node contains an id, an (initially) empty list of children and pointer to his parent node. It orders the tree such that each parent has his children in his children list and returns the root(s). For example, the following input:

[[{"id": 10,
    "children": [],
    "parent_id": null}],
  [{"id": 12,
    "children": [],
    "parent_id": 10},
   {"id": 18,
    "children": [],
    "parent_id": 10},
   {"id": 13,
    "children": [],
    "parent_id": 10}],
  [{"id": 17,
    "children": [],
    "parent_id": 12},
   {"id": 16,
    "children": [],
    "parent_id": 13},
   {"id": 15,
    "children": [],
    "parent_id": 12}]}]

Would be converted to the following output:

[{"id": 10,
  "children":
   [{"id": 12,
     "children":
      [{"id": 17,
        "children": [],
        "parent_id": 12},
       {"id": 15,
        "children": [],
        "parent_id": 12}],
     "parent_id": 10},
    {"id": 18,
     "children": [],
     "parent_id": 10},
    {"id": 13,
     "children":
      [{"id": 16,
        "children": [],
        "parent_id": 13}],
     "parent_id": 10}],
  "parent_id": null}]

The code:

def build_tree(levels):
            ids_to_nodes = []
            for i in range(len(levels)):
                    level = levels[i]
                    ids_to_nodes.append({})
                    for node in level:
                            ids_to_nodes[i][node["id"]] = node

                    if i > 0:
                            for node in level:
                                    levels[i - 1][node["parent_id"]]["children"].append(node)

            return levels[0].values()

The closest I got to implementing this in elixir is

def fix_level(levels, ids_to_nodes, i) do
      if i < length(levels) do
              level = Enum.at(levels, i)
              new_level =
              Enum.reduce level, %{},  fn node, acc ->
                Map.put(acc, node["id"], node)
              end
              ids_to_nodes = ids_to_nodes ++ [new_level]

              if i > 0 do
                Enum.reduce level, Enum.at(ids_to_nodes, i - 1)[node["parent_id"]], fn node, acc ->
                  Map.put(acc, "children", Enum.at(ids_to_nodes, i - 1)[node["parent_id"]]["children"] ++ [node]) # Doesn't work coz creates new map
                end
              end

              fix_level(params, ids_to_nodes, i + 1)
      end
      Map.values(ids_to_nodes[0])
    end


  def fix(levels) do
    fix_level(levels, ids_to_nodes, 0)
  end

I'm aware that the code is very inefficient in a lot of places, particularly around ends of lists - but I'm not sure how to rewrite them in an efficient manner, and more importantly, I'm totally blocked at the marked line. I think I'm thinking too much in an imperative / object oriented way. Help in understanding functional programming will be appreciated.

Try to use recursion instead of loops, start at nodes which have no parent_id(or nil) and recursively build subtrees for each of their children.

The below code is simplistic but is mostly self-explanatory.

It gets the children of the current parent_id(which is nil for root nodes) and builds subtrees for each of its child nodes.

%{map | key1: val1, key2: val2} is Elixir short-hand for updating maps

defmodule TestModule do
    def build_tree(levels, parent_id \\ nil) do
        levels
        |> Enum.filter(& &1.parent_id == parent_id) # get children of parent_id
        |> Enum.map(fn level ->
            %{level | children: build_tree(levels, level.id)} # recursively build subtrees of current level
        end)
        # we should now have child nodes of parent_id with their children populated
    end
end

# sample input
levels = [
    %{id: 1, parent_id: nil, children: []},
    %{id: 2, parent_id: 1, children: []},
    %{id: 3, parent_id: 2, children: []},
    %{id: 4, parent_id: 2, children: []},
    %{id: 5, parent_id: 4, children: []},
    %{id: 6, parent_id: 1, children: []},
    %{id: 7, parent_id: 6, children: []},
    %{id: 8, parent_id: 6, children: []},
    %{id: 9, parent_id: nil, children: []},
    %{id: 10, parent_id: 9, children: []},
]

TestModule.build_tree(levels)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM