Вложенный хэш через рекурсию в Ruby - PullRequest
0 голосов
/ 06 февраля 2019

Я пытался программно создать вложенный хэш по умолчанию в Ruby, в основном это сокращение от Ruby:

h = Hash.new {|h,k| h[k] = Hash.new}

Я хотел бы расширить это для работы на столько уровней, сколько необходимо,Я сделал следующую функцию:

def nested_hash(level, default={})
   return default if level == 0
   return Hash.new{ |h,k| h[k] = nested_hash(level - 1, default) }
end

Похоже, что она работает правильно, но я сталкиваюсь со следующей проблемой при создании нескольких ключей

h = nested_hash(1)
h[0][1] = [1, 2, 3] # h is {0=>{1=>[1, 2, 3]}}
h[2] # should give a new Hash, but returns {1=>[1, 2, 3]}
h # {0=>{1=>[1, 2, 3]}, 2=>{1=>[1, 2, 3]}} 

Почему для функции меняется значение по умолчаниюи становится ранее установленным значением?

РЕДАКТИРОВАТЬ

Я нашел решение, которое работает:

def nested_hash(level, default={})
    return Hash.new{ |h,k| h[k] = default } if level <= 1
    Hash.new{ |h,k| h[k] = nested_hash(level - 1, default) }
end

Неважно, это не 'я не могу работать аналогичным образом:

h = nested_hash(1)
h[0][1] = [1, 2, 3]
h[2][0] # nil
h # {0=>{1=>[1, 2, 3]}, 2=>{1=>[1, 2, 3]}}

Я все еще не понимаю, почему исходные значения по умолчанию были разделены между клавишами.

Ответы [ 2 ]

0 голосов
/ 06 февраля 2019

Просто из любопытства:

hash =
  Hash.new do |h, k|
    h[k] = h.dup.clear.extend(Module.new do
      define_method(:level, ->{ h.level - 1 })
    end).tap { |this| raise "?" if this.level <= 0 }
  end.extend(Module.new { define_method(:level, ->{ 5 }) })

#⇒ {}

hash["0"]["1"]["2"]["3"]
#⇒ {}
hash["0"]["1"]["2"]["3"]["4"]
#⇒ RuntimeError: "?"

Или, как функция:

def nhash lvl
  Hash.new do |h, k|
    h[k] = h.dup.clear.extend(Module.new do
      define_method(:level, ->{ h.level - 1 })
    end).tap { |this| raise "?" if this.level < 0 }
  end.extend(Module.new { define_method(:level, ->{ lvl }) })
end

В результате:

✎ h = nhash 2
#⇒ {}
✎ h[0][1] = [1, 2, 3]
#⇒ [1, 2, 3]
✎ h[2][0]
#⇒ {}
✎ h[2][0][5]
#⇒ RuntimeError: ?

Можно сброситьпроцедура по умолчанию вместо повышения при необходимости.

Единственный недостаток этого подхода, при попытке вызвать уровень выше разрешенного, будут созданы все промежуточные пустые хэши.Это также может быть преодолено путем определения метода, накопления пути (вместо простого возврата уровня) и удаления пустых родителей перед повышением.

0 голосов
/ 06 февраля 2019

Для этого вы можете использовать default_proc.Наилучшим объяснением является то, что приведено в документации:

Если Hash :: new был вызван с блоком, вернуть этот блок, в противном случае вернуть nil.

Так что в этомВ этом случае каждый новый ключ хеш-функции создается с родительским хеш-значением по умолчанию.

Например:

hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }

Таким образом, каждый вложенный уровень будет иметь одинаковое значение по умолчанию:

hash[0][1] = [1, 2, 3]
hash[:first_level] # => {}
hash[:first_level][:second_level] # => {}
hash[2] # => {}
hash # => {0=>{1=>[1, 2, 3]}, :first_level=>{:second_level=>{}}, 2=>{}}

Редактирование на основе комментария:

В этом случае вы можете использовать что-то уродливое, хотя и работающее, например:

hash = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = raise('Too Deep') } } }
hash[1] # => {}
hash[1][2] # => {}
hash[1][2][3] # => RuntimeError: Too Deep
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...