Ruby: What is the order of keys/values returned by Hash.keys and Hash.values methods?
The top of the Ruby 1.9.2 documentation for the Hash class declares:
Hashes enumerate their values in the order that the corresponding keys were inserted.
Cursory tests suggest that this does indeed apply to both Hash#keys
and Hash#values
, although the corresponding documentation for those methods doesn't seem to specify it.
Ruby Hash .keys and .values, safe to assume same order?
Yes. According to the Ruby Docs for Hash, "Hashes enumerate their values in the order that the corresponding keys were inserted." So you should always get the same order for a hash if it is created in the same way.
Hash ordering preserved between iterations if not modified?
Prior to 1.9, behavior of enumerated hashes was not in the ruby specification and therefore was up to implementation -- basically, hash enumeration behavior/pattern was undefined by the language and implementations could really do whatever they want (random? sorted? insertion order? different method every time? anything goes!)
1.9+, hash enumeration is specified by the language to be in the order of insertion, so if you know your platform is 1.9+, you can rely on it.
RubySpec
Is order of a Ruby hash literal guaranteed?
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 Ruby Language Specification
- the RubySpec project
- the YARV testsuite
- The Ruby Programming Language book by matz and David Flanagan
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.
Order Hash and delete first key-value pair
The method you are looking for is hash.keys
it returns an array of the keys:
hash.delete(hash.keys.min)
EDIT: I've updated the answer to reflect that keys must be sorted first, this has been added in the original question and brought up by @Shadwell in comments to this post.
I replaced hash.keys.sort.first
for hash.keys.min
as suggested by @Cary Swoveland, it is not only more performant but better semantically.
Sort keys in Ruby hash without changing the associated values from their original positions
hash = {
9 => ["Blake Johnson", "Jack Bauer"],
7 => ["Bart Simpson", "Homer Simpson"],
10 => ["Avi Flombaum", "Jeff Baird"]
}
Hash[hash.sort]
{ 7=>["Bart Simpson", "Homer Simpson"],
9=>["Blake Johnson", "Jack Bauer"],
10=>["Avi Flombaum", "Jeff Baird"]
}
How to sort a Ruby Hash alphabetically by keys
Assuming you want the output to be a hash which will iterate through keys in sorted order, then you are nearly there. Hash#sort_by
returns an Array
of Array
s, and the inner arrays are all two elements.
Ruby's Hash
has a constructor that can consume this output.
Try this:
temp = Hash[ temp.sort_by { |key, val| key } ]
or more concisely
temp = temp.sort_by { |key| key }.to_h
If your hash has mixed key types, this will not work (Ruby will not automatically sort between String
s and Symbol
s for instance) and you will get an error message like comparison of Symbol with String failed (ArgumentError). If so, you could alter the above to
temp = Hash[ temp.sort_by { |key, val| key.to_s } ]
to work around the issue. However be warned that the keys will still retain their original types which could cause problems with assumptions in later code. Also, most built-in classes support a .to_s
method, so you may get unwanted results from that (such as unexpected sort order for numeric keys, or other unexpected types).
You could, in addition, convert the keys to Strings
with something like this:
temp = Hash[ temp.map { |key, val| [key.to_s, val] }.sort ]
. . . although this approach would lose information about the type of the original key making it impossible to refer back to the original data reliably.
Related Topics
How to Override Gemfile for Local Development
What's a Rails Plugin, or Ruby Gem, to Automatically Fix English Grammar
Converting Binary Data to String in Ruby
Why Does Ruby Only Permit Certain Operator Overloading
How to Recursively Remove All Keys with Empty Values from (Yaml) Hash
Upgrading to Ruby 2.1.3 on MAC Osx 10.9.5
Loading Class Descendants in Rails Development
Ruby Convert Idn Domain from Punycode to Unicode
How to Handle Utf-8 Email Headers (Like Subject:) Using Ruby
How to Scrape Pages Which Have Lazy Loading
Semifixed: Missing 'Secret_Key_Base' for 'Production' Environment
What's the Difference Between /\P{Alpha}/I and /\P{L}/I in Ruby
Running Heroku Console Does Not Start
HTML Is Read Before Fully Loaded Using Open-Uri and Nokogiri
Rails: How to to Download a File from a Http and Save It into Database