4
\$\begingroup\$

I wrote a method which sorts an array of hashes by given hash keys. The method should put nil values at the end.

def sort(records, *attrs)
 records.sort do |a,b|
 result = 0
 attrs.each do |attr|
 unless a[attr] == b[attr]
 result = if a[attr].nil?
 1
 elsif b[attr].nil?
 -1
 else
 a[attr] <=> b[attr]
 end
 break
 end
 end
 result
 end
end
p sort([{:a => 1},{:a => nil},{:a => 2}], :a)
#=> [{:a=>1}, {:a=>2}, {:a=>nil}]
p sort([{:a => nil},{:a => 'x'},{:a => 'a'}], :a)
#=> [{:a=>"a"}, {:a=>"x"}, {:a=>nil}]

My solution looks quite complex. Is there a better way to achieve the ordering in Ruby?

asked May 11, 2016 at 12:47
\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

Set Unions

Looking at your code, when you loop through the attrs, you are breaking after you find the first key that is in a and in b and in the passed attrs. This is also known as the union of the arrays. As such you can simplify the inner loop with:

(attrs & a.keys & b.keys).first

Shorthand if/elseif syntax

You can use the keyword then in conjunction with if and elsif to cut down on the whitespace of your if-eslif chain. The syntax would look like:

if attr.nil? then 0
elsif a[attr].nil? then 1
elsif b[attr].nil? then -1
else a[attr] <=> b[attr] end

Putting it all together

def sort(records, *attrs)
 records.sort do |a, b|
 attr = (attrs & a.keys & b.keys).first
 if attr.nil? then 0
 elsif a[attr].nil? then 1
 elsif b[attr].nil? then -1
 else a[attr] <=> b[attr] end
 end
end
answered May 11, 2016 at 14:34
\$\endgroup\$
1
  • \$\begingroup\$ Thanks. I replaced the union line with attr = attrs.find { |e| a[e] != b[e] }, which also works. \$\endgroup\$ Commented May 11, 2016 at 14:50
4
\$\begingroup\$

You can create temporary sorting columns:

def sort records, *attrs
 records.sort_by do |h|
 h.values_at(*attrs).map do |v|
 v.nil? ? [2] : [1, v]
 end
 end
end

Here I added columns with values 1 or 2 to the left for higher priority -- could add to the right or even in between for more complex sorting.

answered May 11, 2016 at 18:40
\$\endgroup\$
2
  • 2
    \$\begingroup\$ The -1,0,1 values are the the result of the manual a<=>b comparison happening in the sort block. They are the expected return values. \$\endgroup\$ Commented May 11, 2016 at 19:42
  • \$\begingroup\$ @Zack, oh, understood. Rarely using #sort I forgot about that. Thank you for note. \$\endgroup\$ Commented May 12, 2016 at 1:27
1
\$\begingroup\$

Thanks for the suggestions. After all I implemented it the following way. It combines the solutions of Zack and Nakilon.

def sort(records, *attrs)
 records.sort do |a,b|
 k = attrs.find { |e| a[e] != b[e] } # 1.
 k ? [a[k] ? 0 : 1, a[k]] <=> [b[k] ? 0 : 1, b[k]] : 0 # 2.
 end
end
  1. Select the attribute that differs (Similar to the union idea of Zack)
  2. Introduce a pseudo value for comparison with nil values (Taken from Nakilons post)
answered May 12, 2016 at 6:26
\$\endgroup\$
3
  • \$\begingroup\$ What's wrong why Nakilon's answer? Using sort is kind of clunky when sort_by (a higher-level abstraction) can do the job just fine. \$\endgroup\$ Commented May 12, 2016 at 7:26
  • \$\begingroup\$ @tokland Nothing is wrong. It does the nil extra value for every attribute. When selecting the attribute that differs, you have to it just one time. It's only style not correctness. On the other hand, my solution has some redunancy (x[k] ? 0 : 1) that Nakilon approach avoids. :-) \$\endgroup\$ Commented May 12, 2016 at 8:21
  • \$\begingroup\$ While shorter, I feel like this version is much harder to read and understand. \$\endgroup\$ Commented May 12, 2016 at 12:17

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.