web-dev-qa-db-fra.com

Ruby comportement de la valeur par défaut du hachage

Je passe par Ruby Koans, et j'ai frappé le # 41 qui je crois est le suivant:

def test_default_value_is_the_same_object
  hash = Hash.new([])

  hash[:one] << "uno"
  hash[:two] << "dos"

  assert_equal ["uno","dos"], hash[:one]
  assert_equal ["uno","dos"], hash[:two]
  assert_equal ["uno","dos"], hash[:three]

  assert_equal true, hash[:one].object_id == hash[:two].object_id
end

Il ne pouvait pas comprendre le comportement, je l'ai donc recherché sur Google et j'ai trouvé comportement étrange Ruby lors de l'utilisation de la valeur par défaut de Hash, par exemple Hash.new ([]) qui répondait bien à la question .

Je comprends donc comment cela fonctionne, ma question est, pourquoi une valeur par défaut telle qu'un entier qui est incrémenté n'est-elle pas modifiée pendant l'utilisation? Par exemple:

puts "Text please: "
text = gets.chomp

words = text.split(" ")
frequencies = Hash.new(0)
words.each { |Word| frequencies[Word] += 1 }

Cela prendra l'entrée de l'utilisateur et comptera le nombre de fois où chaque mot est utilisé, cela fonctionne car la valeur par défaut de 0 est toujours utilisée.

J'ai le sentiment que cela a à voir avec le << opérateur mais j'aimerais une explication.

40
Jake Sellers

Les autres réponses semblent indiquer que la différence de comportement est due au fait que Integers est immuable et Arrays est mutable. Mais c'est trompeur. La différence n'est pas que le créateur de Ruby a décidé de rendre l'un immuable et l'autre mutable. La différence est que vous, le - programmeur a décidé de muter l'un mais pas l'autre.

La question n'est pas de savoir si Arrays sont mutables, la question est de savoir si vous mute it.

Vous pouvez obtenir les deux comportements que vous voyez ci-dessus, simplement en utilisant Arrays. Observer:

Un Array par défaut avec mutation

hsh = Hash.new([])

hsh[:one] << 'one'
hsh[:two] << 'two'

hsh[:nonexistent]
# => ['one', 'two']
# Because we mutated the default value, nonexistent keys return the changed value

hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!

Un Array par défaut sans mutation

hsh = Hash.new([])

hsh[:one] += ['one']
hsh[:two] += ['two']
# This is syntactic sugar for hsh[:two] = hsh[:two] + ['two']

hsh[:nonexistant]
# => []
# We didn't mutate the default value, it is still an empty array

hsh
# => { :one => ['one'], :two => ['two'] }
# This time, we *did* mutate the hash.

Un nouveau Array différent à chaque fois avec mutation

hsh = Hash.new { [] }
# This time, instead of a default *value*, we use a default *block*

hsh[:one] << 'one'
hsh[:two] << 'two'

hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.

hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!


hsh = Hash.new {|hsh, key| hsh[key] = [] }
# This time, instead of a default *value*, we use a default *block*
# And the block not only *returns* the default value, it also *assigns* it

hsh[:one] << 'one'
hsh[:two] << 'two'

hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.

hsh
# => { :one => ['one'], :two => ['two'], :nonexistent => [] }
106
Jörg W Mittag

C'est parce que Array in Ruby est un objet modifiable, vous pouvez donc le changer d'état interne, mais Fixnum n'est pas modifiable. Ainsi, lorsque vous incrémentez la valeur à l'aide de += en interne, il obtient cela (supposons que i est notre référence à Fixnum objet):

  1. obtenir un objet référencé par i
  2. obtenir sa valeur interne (laisse le nommer raw_tmp)
  3. créer un nouvel objet dont la valeur interne est raw_tmp + 1
  4. attribuer une référence à l'objet créé à i

Comme vous pouvez le voir, nous avons créé un nouvel objet et i fait maintenant référence à quelque chose de différent qu'au début.

En revanche, lorsque nous utilisons Array#<< ça marche comme ça:

  1. obtenir un objet référencé par arr
  2. à son état interne ajouter un élément donné

Donc, comme vous pouvez le voir, c'est beaucoup plus simple, mais cela peut provoquer des bugs. L'un d'eux que vous avez dans votre question, un autre est la course aux threads lorsque le stand essaie d'ajouter simultanément 2 éléments ou plus. Parfois, vous pouvez terminer avec seulement certains d'entre eux et avec des thrashes en mémoire, lorsque vous utilisez += sur les tableaux également, vous vous débarrasserez de ces deux problèmes (ou du moins minimiserez l'impact).

3
Hauleth

Dans doc , la définition d'une valeur par défaut a le comportement suivant:

Renvoie la valeur par défaut, la valeur qui serait retournée par hsh si la clé n'existait pas dans hsh. Voir aussi Hash :: new et Hash # default =.

Par conséquent, chaque fois que frequencies[Word] n'est pas défini, la valeur de cette clé individuelle est définie sur 0.

La raison de la différence entre les deux blocs de code est que les tableaux sont mutables dans Ruby, tandis que les entiers ne le sont pas.

1
user2398029