developer tip

Hash 기본값 (예 : Hash.new ([]))을 사용할 때 이상하고 예상치 못한 동작 (값 사라짐 / 변경)

copycodes 2020. 8. 16. 21:00
반응형

Hash 기본값 (예 : Hash.new ([]))을 사용할 때 이상하고 예상치 못한 동작 (값 사라짐 / 변경)


이 코드를 고려하십시오.

h = Hash.new(0)  # New hash pairs will by default have 0 as values
h[1] += 1  #=> {1=>1}
h[2] += 2  #=> {2=>2}

괜찮습니다.하지만 :

h = Hash.new([])  # Empty array as default value
h[1] <<= 1  #=> {1=>[1]}                  ← Ok
h[2] <<= 2  #=> {1=>[1,2], 2=>[1,2]}      ← Why did `1` change?
h[3] << 3   #=> {1=>[1,2,3], 2=>[1,2,3]}  ← Where is `3`?

이 시점에서 해시가 다음과 같을 것으로 예상합니다.

{1=>[1], 2=>[2], 3=>[3]}

하지만 그것과는 거리가 멀다. 무슨 일이 일어나고 있고 내가 기대하는 행동을 어떻게 얻을 수 있습니까?


먼저이 동작은 배열뿐만 아니라 이후에 변경되는 모든 기본값 (예 : 해시 및 문자열)에 적용됩니다.

TL; DR : Hash.new { |h, k| h[k] = [] }가장 관용적 인 솔루션을 원하고 이유는 신경 쓰지 않는 경우 사용하십시오 .


작동하지 않는 것

Hash.new([])작동하지 않는 이유

Hash.new([])작동하지 않는지 자세히 살펴 보겠습니다 .

h = Hash.new([])
h[0] << 'a'  #=> ["a"]
h[1] << 'b'  #=> ["a", "b"]
h[1]         #=> ["a", "b"]

h[0].object_id == h[1].object_id  #=> true
h  #=> {}

기본 객체가 재사용되고 변경되는 것을 볼 수 있습니다 (이는 유일한 기본값으로 전달되기 때문입니다. 해시는 새 기본값을 가져올 방법이 없기 때문입니다). 그러나 키나 값이없는 이유는 무엇입니까? h[1]여전히 우리에게 가치를 주면서도 배열에서 ? 다음은 힌트입니다.

h[42]  #=> ["a", "b"]

[]호출에 의해 반환 된 배열 은 기본값 일 뿐이며, 이번에는 변경해 왔으므로 이제 새 값을 포함합니다. <<해시에 할당하지 않기 때문에 ( =현재 없이 루비에서 할당 할 수 없습니다 ), 실제 해시에 아무것도 넣지 않았습니다. 대신 우리는 사용이 <<=(이다 <<+=이다 +) :

h[2] <<= 'c'  #=> ["a", "b", "c"]
h             #=> {2=>["a", "b", "c"]}

이것은 다음과 같습니다.

h[2] = (h[2] << 'c')

Hash.new { [] }작동하지 않는 이유

using Hash.new { [] }은 원래 기본값을 재사용하고 변경하는 문제를 해결하지만 (주어진 블록이 매번 호출되어 새 배열을 반환하므로) 할당 문제는 해결되지 않습니다.

h = Hash.new { [] }
h[0] << 'a'   #=> ["a"]
h[1] <<= 'b'  #=> ["b"]
h             #=> {1=>["b"]}

작동하는 것

할당 방법

우리가 항상 사용하는 기억한다면 <<=, 다음 Hash.new { [] } 입니다 가능한 솔루션,하지만 조금 이상한와 (내가 본 적이 비 관용적의 <<=야생에서 사용되지 않습니다). 또한 <<실수로 사용 하면 미묘한 버그가 발생하기 쉽습니다 .

변경 가능한 방법

상태에 대한 문서Hash.new (내 자신을 강조) :

블록이 지정되면 해시 개체와 키로 호출되며 기본값을 반환해야합니다. 필요한 경우 해시에 값을 저장하는 것은 블록의 책임 입니다.

따라서 다음 <<대신 사용하려면 블록 내에서 해시에 기본값을 저장해야합니다 <<=.

h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a'  #=> ["a"]
h[1] << 'b'  #=> ["b"]
h            #=> {0=>["a"], 1=>["b"]}

이렇게하면 개별 호출 (사용 <<=)에서에 전달 된 블록으로 할당을 효과적으로 이동 Hash.new하여 <<.

이 방법과 다른 방법 사이에는 한 가지 기능적 차이가 있습니다.이 방법은 읽을 때 기본값을 할당합니다 (할당은 항상 블록 내부에서 발생하기 때문). 예를 들면 :

h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1  #=> {:x=>[]}

h2 = Hash.new { [] }
h2[:x]
h2  #=> {}

불변의 방법

Hash.new([])잘 작동하는 동안 작동하지 않는지 궁금 할 것 Hash.new(0)입니다. 핵심은 Ruby의 Numerics는 불변이므로 자연스럽게 제자리에서 변경하지 않습니다. 기본값을 불변으로 취급하면 Hash.new([])사용할 수 있습니다 .

h = Hash.new([].freeze)
h[0] += ['a']  #=> ["a"]
h[1] += ['b']  #=> ["b"]
h[2]           #=> []
h              #=> {0=>["a"], 1=>["b"]}

However, note that ([].freeze + [].freeze).frozen? == false. So, if you want to ensure that the immutability is preserved throughout, then you must take care to re-freeze the new object.


Conclusion

Of all the ways, I personally prefer “the immutable way”—immutability generally makes reasoning about things much simpler. It is, after all, the only method that has no possibility of hidden or subtle unexpected behavior. However, the most common and idiomatic way is “the mutable way”.

As a final aside, this behavior of Hash default values is noted in Ruby Koans.


This isn’t strictly true, methods like instance_variable_set bypass this, but they must exist for metaprogramming since the l-value in = cannot be dynamic.


You're specifying that the default value for the hash is a reference to that particular (initially empty) array.

I think you want:

h = Hash.new { |hash, key| hash[key] = []; }
h[1]<<=1 
h[2]<<=2 

That sets the default value for each key to a new array.


The operator += when applied to those hashes work as expected.

[1] pry(main)> foo = Hash.new( [] )
=> {}
[2] pry(main)> foo[1]+=[1]
=> [1]
[3] pry(main)> foo[2]+=[2]
=> [2]
[4] pry(main)> foo
=> {1=>[1], 2=>[2]}
[5] pry(main)> bar = Hash.new { [] }
=> {}
[6] pry(main)> bar[1]+=[1]
=> [1]
[7] pry(main)> bar[2]+=[2]
=> [2]
[8] pry(main)> bar
=> {1=>[1], 2=>[2]}

This may be because foo[bar]+=baz is syntactic sugar for foo[bar]=foo[bar]+baz when foo[bar] on the right hand of = is evaluated it returns the default value object and the + operator will not change it. The left hand is syntactic sugar for the []= method which won't change the default value.

Note that this doesn't apply to foo[bar]<<=bazas it'll be equivalent to foo[bar]=foo[bar]<<baz and << will change the default value.

Also, I found no difference between Hash.new{[]} and Hash.new{|hash, key| hash[key]=[];}. At least on ruby 2.1.2 .


When you write,

h = Hash.new([])

you pass default reference of array to all elements in hash. because of that all elements in hash refers same array.

if you want each element in hash refer to separate array, you should use

h = Hash.new{[]} 

for more detail of how it works in ruby please go through this: http://ruby-doc.org/core-2.2.0/Array.html#method-c-new

참고URL : https://stackoverflow.com/questions/2698460/strange-unexpected-behavior-disappearing-changing-values-when-using-hash-defa

반응형