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?
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.
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