Установка рубинового хэша .default в список

Я думал, что понял, что метод по умолчанию делает с хешем ...

Задайте значение по умолчанию для ключа, если он не существует:

irb(main):001:0> a = {}
=> {}
irb(main):002:0> a.default = 4
=> 4
irb(main):003:0> a[8]
=> 4
irb(main):004:0> a[9] += 1
=> 5
irb(main):005:0> a
=> {9=>5}

Все хорошо.

Но если я установил по умолчанию пустой список или пустой хеш, я не понимаю его поведения в все ....

irb(main):001:0> a = {}
=> {}
irb(main):002:0> a.default = []
=> []
irb(main):003:0> a[8] << 9
=> [9]                          # great!
irb(main):004:0> a
=> {}                           # ?! would have expected {8=>[9]}
irb(main):005:0> a[8]
=> [9]                          # awesome!
irb(main):006:0> a[9]
=> [9]                          # unawesome! shouldn't this be [] ??

Я надеялся / ожидал такого же поведения, как если бы я использовал оператор || = ...

irb(main):001:0> a = {}
=> {}
irb(main):002:0> a[8] ||= []
=> []
irb(main):003:0> a[8] << 9
=> [9]
irb(main):004:0> a
=> {8=>[9]}
irb(main):005:0> a[9]
=> nil

Кто-нибудь может объяснить, что происходит?

Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
41
0
15 756
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Hash.default используется для установки значения по умолчанию вернулся, когда вы запрашиваете ключ, который не существует. Запись в коллекции не создается для вас только потому, что ее запросили.

Кроме того, значение, которое вы установили для default, является экземпляром объекта (в вашем случае - массивом), поэтому, когда он возвращается, им можно управлять.

a = {}
a.default = []     # set default to a new empty Array
a[8] << 9          # a[8] doesn't exist, so the Array instance is returned, and 9 appended to it
a.default          # => [9]
a[9]               # a[9] doesn't exist, so default is returned

хорошее объяснение, имеет смысл

mat kelcey 10.10.2008 15:01

Я хотел бы отметить, что это поведение отличается от defaultdict Python, где аналогичный код работает нормально.

Stumpy Joe Pete 10.10.2012 11:57

Ух ты, это ужасно и ужасно подвержено ошибкам из-за динамического изменения этого значения по умолчанию. Я просто потратил 3 часа, пытаясь понять, что происходит при попытке установить по умолчанию []. ИМНШО, дефолт не должен так выставляться. Альтернатива (h = Hash.new {| h, k | h [k] = []}) на самом деле не делает того, что я хочу, потому что она всегда создает новый массив, просто ссылаясь на хеш-ключ. Теперь я вернулся к тому, чего пытался избежать в первую очередь: h [k] = [], если только h.has_key? (K) :)

Steeve McCauley 15.11.2013 21:33

Надеюсь, вы, ребята, не проголосовали против этого ответа только из-за этой особенности самого Ruby :) Что не так с этим прекрасно объяснительным ответом? Я сам не полностью разобрался с концепцией, пока не увидел акцент на «возвращенном» и объяснение важности того, что значение default является объектом (чтобы оно сохранялось при последующих вызовах несуществующих клавиш).

Halil Özgür 18.03.2014 17:49

irb(main):002:0> a.default = []
=> []
irb(main):003:0> a[8] << 9
=> [9]                          # great!

С помощью этого оператора вы изменили значение по умолчанию; вы не создали новый массив и не добавили «9». На этом этапе это идентично тому, если бы вы сделали это вместо этого:

irb(main):002:0> a.default = [9]
=> [9]

Поэтому неудивительно, что теперь вы получаете это:

irb(main):006:0> a[9]
=> [9]                          # unawesome! shouldn't this be [] ??

Кроме того, «<<» добавляет к массиву цифру «9»; он не добавил его в хеш, что объясняет это:

irb(main):004:0> a
=> {}                           # ?! would have expected {8=>[9]}

Вместо использования .default вы, вероятно, захотите сделать в своей программе что-то вроде этого:

# Time to add a new entry to the hash table; this might be 
# the first entry for this key..
myhash[key] ||= []
myhash[key] << value

Это очень полезная идиома:

(myhash[key] ||= []) << value

Он даже может быть вложенным:

((myhash[key1] ||= {})[key2] ||= []) << value

Другой способ - сделать:

myhash = Hash.new {|hash,key| hash[key] = []}

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

Я не думаю, что побочный эффект последней техники присутствует в Ruby 1.9.2. myhash = Hash.new {| хеш, ключ | хэш [ключ] = []}; myhash.has_key? (: test) # => ложь

Chris Lowis 04.01.2012 19:24

О, нет, я имел в виду, что puts myhash[:test] или что-то подобное, которое кажется безвредным, теперь приведет к тому, что myhash.has_key?(:test) будет верным.

glenn mcdonald 16.01.2012 07:47

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

h = Hash.new { [] }
h[:missing]
   => []

#But, you should never modify the empty array because it isn't stored anywhere
#A new, empty array is returned every time
h[:missing] << 'entry'
h[:missing]
   => []

Я думаю, что это именно то поведение, которое вы ищете. Это автоматически инициализирует любые новые ключи в хэше массивом:

irb(main):001:0> h = Hash.new{|h, k| h[k] = []}
=> {}
irb(main):002:0> h[1] << "ABC"
=> ["ABC"]
irb(main):003:0> h[3]
=> []
irb(main):004:0> h
=> {1=>["ABC"], 3=>[]}

Гленн Макдональд говорит:

"Другой способ:

myhash = Hash.new {| хеш, ключ | хэш [ключ] = []}

Но у этого есть существенный побочный эффект: запрос о ключе создаст его, который отображает has_key? довольно бесполезно, поэтому я избегаю этого метода ".

на самом деле это не кажется правдой.

irb(main):004:0> a = Hash.new {|hash,key| hash[key] = []}
=> {}
irb(main):005:0> a.has_key?(:key)
=> false
irb(main):006:0> a[:key]
=> []
irb(main):007:0> a.has_key?(:key)
=> true

Доступ ключ создаст его, как я и ожидал. Просто спросите has_key? не.

Если вы действительно хотите иметь бесконечно глубокий хеш:

endless = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
endless["deep"]["in"]["here"] = "hello"

Конечно, как указывает Гленн выше, если вы это сделаете, has_key? теряет смысл, так как всегда будет возвращать истину. Спасибо jbarnette за это.

Другие вопросы по теме