New way of creating hashes in ruby 2.2.0
This is not a new style of hash representation, but an extension of the existing style added in 1.9 in a consistent manner.
In 1.9, you can do this
hash = { symbol_key: 'value' }
and you can also define Symbol
s with otherwise-restricted characters using syntax like this:
sym = :'a-symbol-with-dashes'
However in versions 1.9 to 2.1, the code
hash = { 'a-symbol-with-dashes': 'value' }
is not recognised as valid syntax, instead it you get the exception SyntaxError: (irb):4: syntax error, unexpected ':', expecting =>
Adding support for quoted wrapping around the Symbol
in the hash syntax is most likely for consistency. The options when writing a Symbol literal with the short hash key syntax are now the same as when writing the same literal outside of the hash (other than where you put the colon)
Ruby hashes, getting a key which programatically changes
Actually it's almost the same:
my_key = :name
my_hash = { name: 'Joe', age: 52 }
my_hash[my_key]
#=> "Joe"
See http://www.ruby-doc.org/core-2.1.1/Hash.html
Is { 'symbol name': some value } valid Ruby 2 syntax for Hashes?
{ :my_key => "my value" }
{ my_key: "my value" }
{ :'my_key' => "my value" }
{ :"my_key" => "my value" }
None of the above lines uses 2.x-only syntax. They are all also valid 1.9 syntax. (See demonstration.)
{ "my_key": "my value" }
{ 'my_key': "my value" }
That's feature request #4276 which landed in 2.2. That means it's invalid syntax in 2.1 or even older versions. It also means that an implementation that claims to implement 2.2 has to support it.
Conditionally extract hashes from array of hashes
This works:
x.map {|a| a['creationDate'] if a['createdBy'] == 'test_user1'}.compact.max
# => 123459
Why doesn't terse way of defining new hashes in Ruby work (they all refer to same object)
In answer to the original question, an easy way to initialize multiple copies would be to use the Array.new(size) {|index| block }
variant of Array.new
a, b = Array.new(2) { Hash.new }
a, b, c = Array.new(3) { Hash.new }
# ... and so on
On a side note, in addition to the assignment mix-up, the other seeming issue with the original is that it appears you might be making the mistake that ==
is comparing object references of Hash
and String
. Just to be clear, it doesn't.
# Hashes are considered equivalent if they have the same keys/values (or none at all)
hash1, hash2 = {}, {}
hash1 == hash1 #=> true
hash1 == hash2 #=> true
# so of course
[Hash.new, Hash.new] == [Hash.new] * 2 #=> true
# however
different_hashes = [Hash.new, Hash.new]
same_hash_twice = [Hash.new] * 2
different_hashes == same_hash_twice #=> true
different_hashes.map(&:object_id) == same_hash_twice.map(&:object_id) #=> false
How to turn a string of keys and values into a Ruby hash?
If you can assume that the quotes around values are properly balanced and you'll never have an escaped quote in a value, it's pretty easy with a regexp:
Hash[*'key1="value1" key2="value2"'.scan(/(\b.*?)=(".*?")/).flatten]
# => {"key1"=>"\"value1\"", "key2"=>"\"value2\""}
Or in Ruby 2.2:
'key1="value1" key2="value2"'.scan(/(\b.*?)=(".*?")/).to_h
Otherwise, if you want to just split on the = and you'll never have a space in a value:
'key1="value1" key2="value2"'.split.map {|pair| pair.split "=", 2 }.to_h
If you might have spaces in values, then you're going to actually need to write a stateful parser.
`Hash()` when creating a hash
It's a method in Kernel
(which contains other methods that you can call directly like Kernel.puts
) - Kernel.Hash
. Don't use it (it's not idiomatic).
Extending a ruby class (hash) with new function (recursive_merge)
Use merge!
rather than attempt to update self
. I don't believe it makes sense to use merge! anywhere but at the top level, so I wouldn't call the bang version recursively. Instead, use merge! at the top level, and call the non-bang method recursively.
It may also be wise to check both values being merged are indeed hashes, otherwise you may get an exception if you attempt to recursive_merge on a non-hash object.
#!/usr/bin/env ruby
class Hash
def recursive_merge(other)
self.merge(other) { |key, value1, value2| value1.is_a?(Hash) && value2.is_a?(Hash) ? value1.recursive_merge(value2) : value2}
end
def recursive_merge!(other)
self.merge!(other) { |key, value1, value2| value1.is_a?(Hash) && value2.is_a?(Hash) ? value1.recursive_merge(value2) : value2}
end
end
h1 = { a: { b:1, c:2 }, d:1 }
h2 = { a: { b:2, d:4 }, d:2 }
h3 = { d: { b:1, c:2 } }
p h1.recursive_merge(h2) # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
p h1.recursive_merge(h3) # => {:a=>{:b=>1, :c=>2}, :d=>{:b=>1, :c=>2}}
p h1.recursive_merge!(h2) # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
p h1 # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
If you have a specific reason to fully merge in place, possibly for speed, you can experiment with making the second function call itself recursively, rather than delegate the recursion to the first function. Be aware that may produce unintended side effects if the hashes store shared objects.
Example:
h1 = { a:1, b:2 }
h2 = { a:5, c:9 }
h3 = { a:h1, b:h2 }
h4 = { a:h2, c:h1 }
p h3.recursive_merge!(h4)
# Making recursive calls to recursive_merge
# => {:a=>{:a=>5, :b=>2, :c=>9}, :b=>{:a=>5, :c=>9}, :c=>{:a=>1, :b=>2}}
# Making recursive calls to recursive_merge!
# => {:a=>{:a=>5, :b=>2, :c=>9}, :b=>{:a=>5, :c=>9}, :c=>{:a=>5, :b=>2, :c=>9}}
As you can see, the second (shared) copy of h1 stored under the key :c is updated to reflect the merge of h1 and h2 under the key :a. This may be surprising and unwanted. Hence why I recommend using recursive_merge
for the recursion, and not recursive_merge!
.
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
First, note that this behavior applies to any default value that is subsequently mutated (e.g. hashes and strings), not just arrays. It also applies similarly to the populated elements in Array.new(3, [])
.
TL;DR: Use Hash.new { |h, k| h[k] = [] }
if you want the most idiomatic solution and don’t care why.
What doesn’t work
Why Hash.new([])
doesn’t work
Let’s look more in-depth at why Hash.new([])
doesn’t work:
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 #=> {}
We can see that our default object is being reused and mutated (this is because it is passed as the one and only default value, the hash has no way of getting a fresh, new default value), but why are there no keys or values in the array, despite h[1]
still giving us a value? Here’s a hint:
h[42] #=> ["a", "b"]
The array returned by each []
call is just the default value, which we’ve been mutating all this time so now contains our new values. Since <<
doesn’t assign to the hash (there can never be assignment in Ruby without an =
present†), we’ve never put anything into our actual hash. Instead we have to use <<=
(which is to <<
as +=
is to +
):
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
This is the same as:
h[2] = (h[2] << 'c')
Why Hash.new { [] }
doesn’t work
Using Hash.new { [] }
solves the problem of reusing and mutating the original default value (as the block given is called each time, returning a new array), but not the assignment problem:
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
What does work
The assignment way
If we remember to always use <<=
, then Hash.new { [] }
is a viable solution, but it’s a bit odd and non-idiomatic (I’ve never seen <<=
used in the wild). It’s also prone to subtle bugs if <<
is inadvertently used.
The mutable way
The documentation for Hash.new
states (emphasis my own):
If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block’s responsibility to store the value in the hash if required.
So we must store the default value in the hash from within the block if we wish to use <<
instead of <<=
:
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
This effectively moves the assignment from our individual calls (which would use <<=
) to the block passed to Hash.new
, removing the burden of unexpected behavior when using <<
.
Note that there is one functional difference between this method and the others: this way assigns the default value upon reading (as the assignment always happens inside the block). For example:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
The immutable way
You may be wondering why Hash.new([])
doesn’t work while Hash.new(0)
works just fine. The key is that Numerics in Ruby are immutable, so we naturally never end up mutating them in-place. If we treated our default value as immutable, we could use Hash.new([])
just fine too:
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.
can i use : in ruby hash
You can't do it. This syntax applies only to Symbol
hash keys. What you can do is allow symbol-based hash as an argument to make_request
method (or check if it isn't allowed already) - or use HashWithIndifferentAccess
from activesupport
gem.
Related Topics
Hardcoded "Require 'Debug'" Can't Find the Sourcefile
What Is a More Ruby-Like Way of Doing This Command
How to Read a Clients Windows Login Name Using Ruby on Rails
How to Create Automatically a Instance of Every Class in a Directory
Incompatible Character Encoding in Rails - How to Just Fail/Skip Sensibly
How to Download File from Google Drive API with Service Account
How to Split a String of Repeated Characters with Uneven Amounts? Ruby
Gitlab: Invocation of Gitlab-Shell
Elegant Way to Only Show Records If They Exist in Rails Erb
Ruby: Calculate Time Difference Between 2 Times
Ruby: Module, Mixins and Blocks Confusing
Restart Rails Server Automatically After Every Change in Controllers
Config Undefined in Environment Specific Configuration Files
How to Use String Methods on Utf-8 Characters