Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort Array of Arrays by length with tiebreaker

I have an Array of Arrays that I want to sort by longest length to shortest. I achieved this easily enough with a sort_by

> a =  [ [1, 2, 9],
         [4, 5, 6, 7],
         [1, 2, 3] ]
> a.sort_by(&:length).reverse # or a.sort_by {|e| e.length}.reverse
=> [[4, 5, 6, 7], [1, 2, 3], [1, 2, 9]]

What I want, however is to have a sort of tie-breaker for lists of equal length. If two lists' lengths are equal, the list whose last entry is greater should come first. So in the above, [1, 2, 9] and [1, 2, 3] should be switched.

I don't care abouth the case where two lists have both equal length and equal last element, they can be in whatever order if that occurs. I don't know if/how I can acheive this with ruby built-in sorting.

like image 378
Ryan Haining Avatar asked Aug 16 '13 01:08

Ryan Haining


3 Answers

You can still do this with sort_by, you just need to realize that Ruby arrays compare element-by-element:

ary <=> other_ary → -1, 0, +1 or nil

[...]

Each object in each array is compared (using the <=> operator).

Arrays are compared in an “element-wise” manner; the first two elements that are not equal will determine the return value for the whole comparison.

That means that you can use arrays as the sort_by key, then throw in a bit of integer negation to reverse the sort order and you get:

a.sort_by { |e| [-e.length, -e.last] }

That will give you the [[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]] that you're looking for.

If you're not using numbers so the "negation to reverse the order" trick won't work, then use Shaunak's sort approach.

like image 105
mu is too short Avatar answered Nov 11 '22 10:11

mu is too short


There you go :

a =  [ [1, 2, 9],[4, 5, 6, 7],[1, 2, 3] ]
a.sort { |a, b| (b.count <=> a.count) == 0 ? (b.last <=> a.last): (b.count <=> a.count)  } 

That should give you:

[[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]]

How this works: we pass a block to sort function, which first checks if the array length is same, if not it continues to check for last element.

like image 42
Shaunak Avatar answered Nov 11 '22 11:11

Shaunak


You could use

a.sort_by {|i| [i.length, i.last] }.reverse
# => [[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]]
like image 28
Santhosh Avatar answered Nov 11 '22 12:11

Santhosh