使用散列数组的树节点

时间:2017-05-27 16:35:35

标签: ruby-on-rails ruby ruby-on-rails-4

我有以下哈希数组:

[
  {key: 1, reference: 'reference', path: '.../public/shared/file_1.txt', type: 'public'},
  {key: 2, reference: 'reference', path: '.../public/shared/file_2.txt', type: 'public'},
  {key: 3, reference: 'reference', path: '.../public/shared/sub_folder/file_3.txt', type: 'public'},
  {key: 4, reference: 'reference', path: '.../public/shared/sub_folder/file_4.txt', type: 'public'},
  {key: 5, reference: 'reference', path: '.../log/file_5.txt', type: 'log'},
  {key: 5, reference: 'reference', path: '.../tmp/cache/file_6.txt', type: 'log'},
]

然后使用每个节点的路径结构,我想创建一个这样的树:

[
  { 
    key: 'root', folder: true, title: 'root',
    children: [
                {
                  key: 'public', folder: true, title: 'public',
                  children: [
                              {
                                key: 'shared', folder: true, title: 'shared',
                                children: [ 
                                            { key: 1, title: 'file_1.txt' },
                                            { key: 2, title: 'file_2.txt' },
                                            {
                                              key: 'sub_folder', folder: true, title: 'sub_folder',
                                              children: [
                                                { key: 3, title: 'file_3.txt' },
                                                { key: 4, title: 'file_4.txt' }
                                              ]
                                            }
                                          ]
                              }
                            ]
                },
                {
                  key: 'log', folder: true, title: 'log', children: [ { key: 5, title: 'file_5.txt' } ]
                },
                {
                  key: 'tmp', folder: true, title: 'tmp',
                  children: [
                              { key: 'cache', folder: true, title: 'cache', children: [ { key: 6, title: 'file_6.txt' } }
                            ]
                }
              ]
  }
]

是否可以使用每个节点的路径创建该结构?我使用Ruby on Rails。

2 个答案:

答案 0 :(得分:1)

您可以像online repl一样定义类:

class Tree 
  attr_reader :array, :tree

  def initialize(array)
    @array = array
    create_tree!
  end

  private

  def create_tree!
    @tree = []
    array.each do |hash|
      process_path(hash[:path].gsub('...', 'root').split('/'))
    end
  end

  def process_path(array)
    current = @tree
    array.each do |folder_or_file|
      if persist = current.find { |hash| hash[:key] == folder_or_file }
        current = persist[:children]
      else
        current << {
          key: folder_or_file,
          folder: folder_or_file['.'].nil?,
          title: folder_or_file,
          children: []
        }
        current = current.last[:children]
      end
    end
  end
end

arr = [
  {key: 1, reference: 'reference', path: '.../public/shared/file_1.txt', type: 'public'},
  {key: 2, reference: 'reference', path: '.../public/shared/file_2.txt', type: 'public'},
  {key: 3, reference: 'reference', path: '.../public/shared/sub_folder/file_3.txt', type: 'public'},
  {key: 4, reference: 'reference', path: '.../public/shared/sub_folder/file_4.txt', type: 'public'},
  {key: 5, reference: 'reference', path: '.../log/file_5.txt', type: 'log'},
  {key: 5, reference: 'reference', path: '.../tmp/cache/file_6.txt', type: 'log'},
]

Tree.new(arr).tree

答案 1 :(得分:0)

我提供了一个递归解决方案。

<强>代码

def doit(arr)
  a = arr.map do |g|
    *front, last = ['root', *g[:path][4..-1].split('/')]
    [front, { key: g[:key], title: last }]
  end
  recurse a
end

def recurse(a)
  a.reject { |dirs, _| dirs.empty? }. 
    group_by { |dirs,_| dirs.shift }.
    map do |dir,v|
      empty, non_empty = v.partition { |d,_| d.empty? }
      { key: dir, folder: true, title: dir,
        children: [*empty.map(&:last), *recurse(non_empty)] }
    end
end

示例

arr = [
  { key: 1, reference: 'reference', path: '.../public/shared/file_1.txt',
    type: 'public' },
  { key: 2, reference: 'reference', path: '.../public/shared/file_2.txt',
    type: 'public' },
  { key: 3, reference: 'reference', path: '.../public/shared/sub_folder/file_3.txt',
    type: 'public' },
  { key: 4, reference: 'reference', path: '.../public/shared/sub_folder/file_4.txt',
    type: 'public' },
  { key: 5, reference: 'reference', path: '.../log/file_5.txt',
    type: 'log' },
  { key: 6, reference: 'reference', path: '.../tmp/cache/file_6.txt',
    type: 'log' }
]

我们现在可以从arr构建所需的数组:

doit arr 
  #=> [{:key=>"root", :folder=>true, :title=>"root", :children=>
  #     [{:key=>"public", :folder=>true, :title=>"public", :children=>
  #       [{:key=>"shared", :folder=>true, :title=>"shared", :children=>
  #         [{:key=>1, :title=>"file_1.txt"},
  #          {:key=>2, :title=>"file_2.txt"},
  #          {:key=>"sub_folder", :folder=>true, :title=>"sub_folder",
  #           :children=>[{:key=>3, :title=>"file_3.txt"},
  #                       {:key=>4, :title=>"file_4.txt"}
  #                      ]
  #          }
  #         ]
  #        }
  #       ]
  #      },
  #      {:key=>"log", :folder=>true, :title=>"log",
  #       :children=>[{:key=>5, :title=>"file_5.txt"}]
  #      },
  #      {:key=>"tmp", :folder=>true, :title=>"tmp",
  #       :children=>[{:key=>"cache", :folder=>true, :title=>"cache",
  #                    :children=>[{:key=>6, :title=>"file_6.txt"}]
  #                   }
  #                  ]
  #      }
  #     ]
  #    }
  #   ]

<强>解释

步骤如下(对于示例中的arr),

doit

  a = arr.map do |g|
    *front, last = ['root', *g[:path][4..-1].split('/')]
    [front, { key: g[:key], title: last }]
  end
    #=> [[["root", "public", "shared"], {:key=>1, :title=>"file_1.txt"}],
    #    [["root", "public", "shared"], {:key=>2, :title=>"file_2.txt"}],
    #    [["root", "public", "shared", "sub_folder"], {:key=>3, :title=>"file_3.txt"}],
    #    [["root", "public", "shared", "sub_folder"], {:key=>4, :title=>"file_4.txt"}],
    #    [["root", "log"], {:key=>5, :title=>"file_5.txt"}],
    #    [["root", "tmp", "cache"], {:key=>6, :title=>"file_6.txt"}]]

打破这一点,我们执行以下计算。

arr的第一个元素传递给map's block and becomes the value of the block variable g`:

  g = arr.first
    #=> {:key=>1, :reference=>"reference",
    #    :path=>".../public/shared/file_1.txt", :type=>"public"}

然后执行块计算。

b = g[:path]
  #=> ".../public/shared/file_1.txt"
c = b[4..-1]
  #=> "public/shared/file_1.txt"
d = c.split('/')
  #=> ["public", "shared", "file_1.txt"]
e = ['root', *d]
  #=> ["root", "public", "shared", "file_1.txt"]
*front, last = e
  #=> ["root", "public", "shared", "file_1.txt"]
front
  #=> ["root", "public", "shared"]
last
  #=> "file_1.txt"
f = g[:key]
  #=>
[front, { key: f, title: last }]
  #=> [["root", "public", "shared"], {:key=>1, :title=>"file_1.txt"}]

arr的其余元素的映射类似。

上面的数组a被传递到recurse。第一步是删除[d, h]为空的任何元素dh是一个目录数组,d哈希)。在将一个或多个哈希值添加到数组:children之后,这是一个需要更深入递归的技术要求。

m = a.reject { |dirs, _| dirs.empty? }
  #=> a (no elements are removed)

下一步是[dirs, h]的第一个元素m的组元素dirs。我在下面的块中使用dirs.shift也从数组dirs中删除了该元素。

n = m.group_by { |dirs,_| dirs.shift }
  #=> {"root"=>[ 
  #     [["public", "shared"], {:key=>1, :title=>"file_1.txt"}],
  #     [["public", "shared"], {:key=>2, :title=>"file_2.txt"}],
  #     [["public", "shared", "sub_folder"], {:key=>3, :title=>"file_3.txt"}],
  #     [["public", "shared", "sub_folder"], {:key=>4, :title=>"file_4.txt"}],
  #     [["log"], {:key=>5, :title=>"file_5.txt"}],
  #     [["tmp", "cache"], {:key=>6, :title=>"file_6.txt"}]
  #    ]
  #   }

n的第一个元素现在传递给map块并分配了块变量:

dir, v = n.first
  #=> ["root", [
  #      [["public", "shared"], {:key=>1, :title=>"file_1.txt"}],
  #      [["public", "shared"], {:key=>2, :title=>"file_2.txt"}], 
  #      [["public", "shared", "sub_folder"], {:key=>3, :title=>"file_3.txt"}],
  #      [["public", "shared", "sub_folder"], {:key=>4, :title=>"file_4.txt"}],
  #      [["log"], {:key=>5, :title=>"file_5.txt"}],
  #      [["tmp", "cache"], {:key=>6, :title=>"file_6.txt"}]
  #    ]
  #   ]
dir
  #=> "root"
v #=> [[["public", "shared"], {:key=>1, :title=>"file_1.txt"}],
  #    ...
  #    [["tmp", "cache"], {:key=>6, :title=>"file_6.txt"}]
  #   ]

然后执行块计算。

  empty, non_empty =  v.partition { |d,_| d.empty? }
  empty
    #=> []
  non_empty
    #=> [[["public", "shared"], {:key=>1, :title=>"file_1.txt"}],
    #    [["public", "shared"], {:key=>2, :title=>"file_2.txt"}], 
    #    [["public", "shared", "sub_folder"], {:key=>3, :title=>"file_3.txt"}],
    #    [["public", "shared", "sub_folder"], {:key=>4, :title=>"file_4.txt"}],
    #    [["log"], {:key=>5, :title=>"file_5.txt"}],
    #    [["tmp", "cache"], {:key=>6, :title=>"file_6.txt"}]
    #   ]
  p = empty.map(&:last)
    #=> []
  { key: dir, folder: true, title: dir,
    children: [*p, *recurse(non_empty)] }
    #=> { key: 'root', folder: true, title: 'root',
    #     children: [*[], *recurse(non_empty)] }

最后一个键:children的值减少为[*recurse(non_empty)]。如图所示,recurse现在以递归方式调用,参数为non-empty

剩下的计算是相似的,但是当recurse传递一个数组,其中有一个或多个元素dirs数组包含单个元素时,事情会有所不同,导致相关的哈希被添加到一个数组,它是一个键:children的值。要完全理解计算,可能需要在代码中添加一些puts语句。