1

I have a list of files sorted in an array, like:

arr = ["./a.txt", "./b.txt", "./bar/z.php", "./foo/hello/y.php", "./foo/x.php"]

How to create a dynatree object out of this? A dynatree object for it, should look like:

[{"name"=>".", "title" => ".", "isDir" => true, "children" => 
  [{"name"=>"a.txt", "title" => "a.txt"}, 
    {"name"=>"b.txt", "title" => "b.txt"}, 
    {"name" => "bar", "title"=>"bar", "isDir"=>true, "children" => 
      [{"name" => "z.php", "title" => "z.php"}, 
        {"name" => "foo", "title" => "foo", "isDir" => true, "children" => 
          [{"name" => "x.php", "title" => "x.php"},
            { "name" => "hello", "title" => "hello", "children" => 
              [{"name" => "y.php", "title"=>"y.php"}
              ]
            }
          ]
        }
      ]
    }
  ]
}]

PS: This question might seem a lazy post, but I have spent more than 20 hrs now solving this. So any help will be greatly appreciated. Thanks.

shivam
  • 16,048
  • 3
  • 56
  • 71

2 Answers2

2

I like to choose a more modular approach. First, I'd build a method make_tree that converts a list of file paths to a nested hash:

require 'pathname'

def insert_node(tree, parts)
  head, *tail = parts
  tree[head] ||= {}
  insert_node tree[head], tail unless tail.empty?
  tree
end

def make_tree(paths)
  paths.reduce({}) do |tree, file| 
    insert_node tree, Pathname(file).each_filename.to_a
  end
end

Here's an example - this output will only be used as intermediate result later on:

paths = ["./a.txt", "./b.txt", "./bar/z.php", "./foo/hello/y.php", "./foo/x.php"]
tree = make_tree(paths)
#=> {"."=>
#    {"a.txt"=>{},
#     "b.txt"=>{},
#     "bar"=>{"z.php"=>{}},
#     "foo"=>{"hello"=>{"y.php"=>{}}, "x.php"=>{}}}}

Then, we can write a function to convert this nested hash into the "dynatree" representation:

def make_dynatree(tree)
  tree.map do |node, subtree|
    if subtree.empty?
      {"name" => node, "title" => node}
    else
      {"name" => node, "title" => node, "isDir" => true, "children" => make_dynatree(subtree)}
    end
  end
end

And finally:

dynatree = make_dynatree(tree)
#=> [{"name"=>".", "title"=>".", "isDir"=>true, "children"=>
#     [{"name"=>"a.txt", "title"=>"a.txt"},
#      {"name"=>"b.txt", "title"=>"b.txt"},
#      {"name"=>"bar", "title"=>"bar", "isDir"=>true, "children"=>[
#        {"name"=>"z.php", "title"=>"z.php"}]},
#      {"name"=>"foo", "title"=>"foo", "isDir"=>true, "children"=>[
#        {"name"=>"hello", "title"=>"hello", "isDir"=>true, "children"=>[
#          {"name"=>"y.php", "title"=>"y.php"}]},
#          {"name"=>"x.php", "title"=>"x.php"}]}]}]
Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
1

My idea is to iterate over each paths. Each path is split by / into parts. The last part is the file, others - directories.

I go through each part adding new directories unless they were already added. In the end of each iteration I switch the context to the next level - children array of the last directory.

File is the last part - I simple append it to the current context.

arr = ["./a.txt", "./b.txt", "./bar/z.php", "./foo/hello/y.php", "./foo/x.php"]

tree = []

arr.each do |path|
  # start at the beginning on each iteration
  current_level = tree

  # split path by '/'
  splitted_path = path.split('/')

  # remember the size of parts in path to distinct files from folders
  size = splitted_path.size

  # iterate over each part of a path
  splitted_path.each_with_index do |node, i|
    if i != size - 1
      # current node is path
      # detect if it is already in the array
      unless current_level.detect { |n| n['name'] == node }
        # if not - append it to array
        current_level << {
          'name' => node,
          'title' => node,
          'isDir' => true,
          'children' => []
        }
      end
      # set the current level to the new node's children array
      current_level = current_level.detect { |n| n['name'] == node }['children']
    else
      # node is a file - append it to the children array on current level
      current_level << {
        'name' => node,
        'title' => node
      }
    end
  end
end

tree
Nikita Chernov
  • 2,031
  • 16
  • 13