У меня есть следующий массив хэшей:

[
  {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.

0
darkcode 27 Май 2017 в 19:35

2 ответа

Лучший ответ

Вы можете определить класс следующим образом: онлайн-реплик:

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
DjezzzL 27 Май 2017 в 17:04

Я предоставил рекурсивное решение.

< Сильный > Код

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 выше передается для рекурсии. Первым шагом является удаление любых элементов [d, h] (d является массивом каталогов, h хеш), для которых 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 операторы в код.

0
Cary Swoveland 28 Май 2017 в 05:58