Ruby, since v1.9, supports a deterministic order when looping through a hash; entries added first will be returned first.
Does this apply to literals, i.e. will { a: 1, b: 2 }
always yield a before b?
I did a quick experiment with Ruby 2.1 (MRI) and it was in fact consistent, but to what extent is this guaranteed by the language to work on all Ruby implementations?
Hashes are inherently unordered. Hashes provide amortized O(1) insertion and retrieval of elements by key, and that's it. If you need an ordered set of pairs, use an array of arrays.
A Hash is a collection of key-value pairs like this: "employee" = > "salary". It is similar to an Array, except that indexing is done via arbitrary keys of any object type, not an integer index.
Equality—Two hashes are equal if they each contain the same number of keys and if each key-value pair is equal to (according to Object#== ) the corresponding elements in the other hash. The orders of each hashes are not compared.
There are couple of locations where this could be specified, i.e. a couple of things that are considered "The Ruby Language Specification":
The ISO spec doesn't say anything about Hash
ordering: it was written in such a way that all existing Ruby implementations are automatically compliant with it, without having to change, i.e. it was written to be descriptive of current Ruby implementations, not prescriptive. At the time the spec was written, those implementations included MRI, YARV, Rubinius, JRuby, IronRuby, MagLev, MacRuby, XRuby, Ruby.NET, Cardinal, tinyrb, RubyGoLightly, SmallRuby, BlueRuby, and others. Of particular interest are MRI (which only implements 1.8) and YARV (which only implements 1.9 (at the time)), which means that the spec can only specify behavior which is common to 1.8 and 1.9, which Hash
ordering is not.
The RubySpec project was abandoned by its developers out of frustration that the ruby-core developers and YARV developers never recognized it. It does, however, (implicitly) specify that Hash
literals are ordered left-to-right:
new_hash(1 => 2, 4 => 8, 2 => 4).keys.should == [1, 4, 2]
That's the spec for Hash#keys
, however, the other specs test that Hash#values
has the same order as Hash#keys
, Hash#each_value
and Hash#each_key
has the same order as those, and Hash#each_pair
and Hash#each
have the same order as well.
I couldn't find anything in the YARV testsuite that specifies that ordering is preserved. In fact, I couldn't find anything at all about ordering in that testsuite, quite the opposite: the tests go to great length to avoid depending on ordering!
The Flanagan/matz book kinda-sorta implicitly specifies Hash
literal ordering in section 9.5.3.6 Hash
iterators. First, it uses much the same formulation as the docs:
In Ruby 1.9, however, hash elements are iterated in their insertion order, […]
But then it goes on:
[…], and that is the order shown in the following examples:
And in those examples, it actually uses a literal:
h = { :a=>1, :b=>2, :c=>3 } # The each() iterator iterates [key,value] pairs h.each {|pair| print pair } # Prints "[:a, 1][:b, 2][:c, 3]" # It also works with two block arguments h.each do |key, value| print "#{key}:#{value} " # Prints "a:1 b:2 c:3" end # Iterate over keys or values or both h.each_key {|k| print k } # Prints "abc" h.each_value {|v| print v } # Prints "123" h.each_pair {|k,v| print k,v } # Prints "a1b2c3". Like each
In his comment, @mu is too short mentioned that
h = { a: 1, b: 2 }
is the same ash = { }; h[:a] = 1; h[:b] = 2
and in another comment that
nothing else would make any sense
Unfortunately, that is not true:
module HashASETWithLogging def []=(key, value) puts "[]= was called with [#{key.inspect}] = #{value.inspect}" super end end class Hash prepend HashASETWithLogging end h = { a: 1, b: 2 } # prints nothing h = { }; h[:a] = 1; h[:b] = 2 # []= was called with [:a] = 1 # []= was called with [:b] = 2
So, depending on how you interpret that line from the book and depending on how "specification-ish" you judge that book, yes, ordering of literals is guaranteed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With