Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting a hash with nil as a key

Tags:

null

ruby

I'm going through this tutorial: http://tutorials.jumpstartlab.com/projects/jsattend.html

In iteration 7, step 3 we get to sort a hash, called state_data, which has nil as a key. The proposed solution is:

state_data = state_data.sort_by{|state, counter| state unless state.nil?}

Unfortunately, this isn't working on ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin11.0.0]. For example:

~% irb
>> things = { nil => "a", 2 => "b", 3 => "c" }
>> things.sort_by { |k, v| k unless k.nil? }
ArgumentError: comparison of NilClass with 2 failed
    from (irb):6:in `sort_by'
    from (irb):6
    from /Users/jacopo/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'

The same goes with the equivalent:

>> things.sort_by { |k, v| k if k }
ArgumentError: comparison of NilClass with 2 failed
    from (irb):3:in `sort_by'
    from (irb):3
    from /Users/jacopo/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'

In the tutorial's case, since it's sorting on the state two letter code, a possible solution is:

state_data = state_data.sort_by{|state, counter| state.nil? ? "ZZ" : state }

which is clearly a hack.

What's the Ruby-way to deal with this?

like image 913
Jacopo Notarstefano Avatar asked Dec 31 '25 16:12

Jacopo Notarstefano


1 Answers

The line

state_data.sort_by { |state, counter| state unless state.nil? }

is effectively equivalent to a simple state_data.sort, because (state unless state.nil?) == state in any case. What you could do is to seperate the proper keys from the nil keys and only sort the former:

state_data.select(&:first).sort + state_data.reject(&:first)

In the more general case, you can also define a custom comparison function like this:

def compare(a, b)
  return a.object_id <=> b.object_id unless a || b
  return -1 unless b
  return 1  unless a
  a <=> b
end

state_data.sort { |a, b| compare(a.first, b.first) }

By quickly looking at this, you see that it's pretty ugly. In fact, you should rather rethink your choice of data structure here. nil keys don't seem very sensible to me.

UPDATE: From looking at the tutorial you linked to, I take it that there is a lot of suboptimal information in there. Take a look at the following piece of "Ruby", for example:

ranks = state_data.sort_by{|state, counter| counter}.collect{|state, counter| state}.reverse
state_data = state_data.sort_by{|state, counter| state}

state_data.each do |state, counter|
  puts "#{state}:\t#{counter}\t(#{ranks.index(state) + 1})"
end

You could write this much more cleanly and get the same result:

rank = state_data.sort_by(&:last)
state_data.sort.each do |data|
  puts "%s:\t%d\t(%d)" % [*data, rank.index(data) + 1]
end

The author also recommends code like

filename = "output/thanks_#{lastname}_#{firstname}.html"
output = File.new(filename, "w")
output.write(custom_letter)

While the Ruby idiom would be:

filename = "output/thanks_#{lastname}_#{firstname}.html"
File.open(filename, 'w') { |f| f.write(custom_letter) }

This shows that the author doesn't seem to be very apt in Ruby. Thus, I can't recommend that tutorial.

like image 125
Niklas B. Avatar answered Jan 02 '26 13:01

Niklas B.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!