我提供了一个递归解决方案。
代码
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 variableg`:
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 为空的所有元素 [d, h](d 是一个目录数组,h 一个哈希)。这是一项技术要求,需要在将一个或多个哈希值添加到 :children 的值的数组中之后进行更深入的递归。
m = a.reject { |dirs, _| dirs.empty? }
#=> a (no elements are removed)
下一步是将m 中的元素[dirs, h] 组合成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 语句。