Zachowanie wartości domyślnej Ruby hash

[[3]}przechodzę przez Ruby Koans i trafiłem na #41, który moim zdaniem jest taki:

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

Nie mógł zrozumieć zachowania, więc Wygooglowałem go i znalazłem dziwne zachowanie Rubiego podczas używania domyślnej wartości Hash, np. Hash.nowe ([]) , które ładnie odpowiedziały na pytanie.

Więc rozumiem, jak to działa, moje pytanie brzmi, dlaczego domyślna wartość, taka jak liczba całkowita, która jest zwiększana, nie zmienia się podczas użytkowania? Na przykład:

puts "Text please: "
text = gets.chomp

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

To zajmie wejście użytkownika i policzy ilość razy każde słowo jest używane, to działa, ponieważ domyślna wartość 0 jest zawsze używany.

Mam wrażenie, że ma to związek z operatorem <<, ale chciałbym wyjaśnić.
 37
Author: Andrew Marshall, 2013-04-23

3 answers

Inne odpowiedzi wydają się wskazywać, że różnica w zachowaniu wynika z tego, że Integersą niezmienne i Arraysą zmienne. Ale to jest mylące. Różnica nie polega na tym, że twórca Ruby postanowił uczynić jedną niezmienną, a drugą mutowalną. Różnica polega na tym, że ty, programista zdecydował się na mutację jednego, ale nie drugiego.

Nie chodzi o to, czy Arrays są mutowalne, tylko o to, czy ty mutujesz to.

Możesz uzyskać oba zachowania, które widzisz powyżej, po prostu za pomocą Array S. Obserwuj:

Jeden domyślny Array z mutacją

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!

Jeden domyślny Array bez mutacji

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.

Nowy, inny Array za każdym razem z mutacją

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 => [] }
 98
Author: Jörg W Mittag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-23 12:09:58

Dzieje się tak dlatego, że Array W Rubim jest obiektem mutowalnym, więc możesz zmienić jego stan wewnętrzny, ale {[1] }nie jest mutowalny. Więc kiedy zwiększasz wartość używając += wewnętrznie, otrzymujesz to (Załóżmy, że i jest naszym odniesieniem do Fixnum obiektu):

  1. get object referred by i
  2. get it internal value (lets name it raw_tmp)
  3. utwórz nowy obiekt o wewnętrznej wartości raw_tmp + 1
  4. przypisanie referencji do utworzonego obiektu do i

Więc jak widzisz, my stworzył nowy obiekt i i odwołuje się teraz do czegoś innego niż na początku.

Z drugiej strony, gdy używamy Array#<< działa to w ten sposób:

  1. get object referred by arr
  2. do stanu wewnętrznego dołącza dany element

Więc jak widać jest to znacznie prostsze, ale może powodować pewne błędy. Jednym z nich masz w swoim pytaniu, innym jest wyścig wątków, gdy booth próbuje jednocześnie dołączyć 2 lub więcej elementów. Sometimes you może się skończyć tylko z niektórymi z nich i z thrashami w pamięci, gdy użyjesz += również na tablicach, pozbędziesz się obu tych problemów(lub przynajmniej zminimalizujesz wpływ).

 3
Author: Hauleth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-02 00:47:19

Z doc Ustawienie wartości domyślnej ma następujące zachowanie:

Zwraca wartość domyślną, wartość, która byłaby zwracana przez HSH, gdyby klucz nie istniał w hsh. Zobacz także Hash:: new oraz Hash # default=.

Dlatego za każdym razem, gdy frequencies[word] nie jest ustawione, wartość dla danego klucza jest ustawiona na 0.

Powodem rozbieżności między dwoma blokami kodu jest to, że tablice są mutowalne w Rubim, podczas gdy liczby całkowite nie.

 1
Author: user2398029,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-23 01:31:08