Элегантный способ создания рекурсивных каталогов - PullRequest
0 голосов
/ 07 декабря 2018

У меня есть структура каталогов, аналогичная приведенной ниже.Он имеет 6 уровней глубины, и в некоторых случаях есть два каталога, которые необходимо создать на одном уровне.Я хотел бы рекурсивно создавать эти каталоги.

pennsylvania
  bucks
    medicine
      pro
        bio
          a
          b
          c
        mental
          a
          b
          c
        physical 
          a
          b
          c
      non_pro
        bio
          a
          b
          c
        mental
          a
          b
          c
        physical 
          a
          b
          c

Уродливый и потенциально ошибочный подход будет выглядеть так:

unless File.exists?("pennsylvania")
  Dir.mkdir "pennsylvania"
end

unless File.exists?("pennsylvania/bucks")
  Dir.mkdir "pennsylvania/bucks"
end

unless File.exists?("pennsylvania/bucks/medicine")
  Dir.mkdir "pennsylvania/bucks/medicine"
end

unless File.exists?("pennsylvania/bucks/medicine/pro")
  Dir.mkdir "pennsylvania/bucks/medicine/pro"
end

И так далее.Вы можете видеть, насколько это неэффективно, когда мы перемещаемся вниз по структуре каталогов, которую необходимо создать.Я ищу более элегантное решение.Примерно так:

Использование класса FileUtils (хотя и не входит в стандартную библиотеку) делает его лучше:

['pro', 'non_pro'].each do |lev1|
  ['bio', 'mental', 'physical'].each do |lev2|
    ['a', 'b', 'c'].each do |lev3|
      FileUtils.mkdir_p "pennsylvania/bucks/medicine/#{lev1}/#{lev2}/#{lev3}"
    end
  end
end

Еще одним преимуществом FileUtils является то, что он не вызывает исключение, если каталог уже существуети, кажется, он не перезаписывает каталоги (и файлы в них), если каталог уже существует.

Второе решение, которое я придумал, - это значительное улучшение первого решения.Но есть ли еще более элегантный способ?

1 Ответ

0 голосов
/ 07 декабря 2018

FileUtils.mkdir_p является хорошим выбором и является частью стандартной библиотеки.

Вы можете использовать Array#product для создания дерева.

levels = ['pro', 'non_pro'].product(
  ['bio', 'mental', 'physical'],
  ['a', 'b', 'c']
)

Тогда вы можете заставить свой блок работать с любым количеством уровней с помощью Array#join.

base = ["pennsylvania","bucks","medicine"]
levels.each do |level|
  FileUtils.mkdir_p( (base + level).join("/") )
end

Обратите внимание, что пока это очень элегантноЭто не самый эффективный способ сделать это.Проблема в том, что каждый вызов FileUtils.mkdir_p будет пытаться создать каждый подкаталог, и если он получит ошибку, проверьте, существует ли он уже.Для небольшого количества каталогов в быстрой файловой системе это нормально.Но для большого дерева или медленной файловой системы, такой как сетевая файловая система, это может снизить производительность.

Для более эффективного использования файловой системы вы должны выполнить что-то вроде этой рекурсии.

levels = [
  ['pennsylvania'],
  ['bucks'],
  ['medicine'],
  ['pro', 'non_pro'],
  ['bio', 'mental', 'physical'],
  ['a', 'b', 'c']
]

def make_subdirs(levels, base = [])
  return if levels.empty?

  levels[0].each { |dir|
    new_base = [*base, dir]
    mkdir_ignore_if_exists(new_base)
    make_subdirs(levels[1..-1], new_base)
  }
end

private def mkdir_ignore_if_exists(dirs)
  Dir.mkdir(dirs.join("/"))
rescue Errno::EEXIST
end

make_subdirs(levels)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...