Sorting an array based on an attribute that may be nil in some elements
Asked Answered
D

3

29

I have an array of objects

[<#a star=1  val=1>, <#a star=nil val=3> , <#a star=2  val=2>]

i need the array to be sorted by time, then by val

[ <#a star=2  val=2>, <#a star=1  val=1>, <#a star=nil val=3> ]

but using the sort_by throws an error because the time is nil.

I am using an ugly way to sort right now, but i am sure there is a nice way to go about it

starred=[]
@answers.each {|a| (starred << a) if a.starred }
@answers=@answers-starred
starred=starred.sort_by {|a| a.starred }.reverse
@answers=starred+@answers
Dorettadorette answered 18/12, 2010 at 0:59 Comment(0)
T
46
starred.sort_by { |a| [a ? 1 : 0, a] }

When it has to compare two elements, it compares an arrays. When Ruby compares arrays (calls === method), it compares 1st element, and goes to the 2nd elements only if the 1st are equal. ? 1 : 0 garantees, that we'll have Fixnum as 1st element, so it should be no error.

If you do ? 0 : 1, nil will appear at the end of array instead of begining.
Here is an example:

irb> [2, 5, 1, nil, 7, 3, nil, nil, 4, 6].sort_by { |i| [i ? 1 : 0, i] }
=> [nil, nil, nil, 1, 2, 3, 4, 5, 6, 7]

irb> [2, 5, 1, nil, 7, 3, nil, nil, 4, 6].sort_by { |i| [i ? 0 : 1, i] }
=> [1, 2, 3, 4, 5, 6, 7, nil, nil, nil]
Trioecious answered 18/12, 2010 at 1:10 Comment(0)
P
8

If you want the nils to appear first (zero-equivalent):

@answers.sort_by { |a| a.star or 0 }

If you want to make them appear last, you can replace the zero with a Max Int, but that feels too hacky. This might be better:

@answers.select(&:starred?).sort_by(&:star) + @answers.reject(&:starred?)

The answer provided by Nakilon is brilliant, though you're essentially sorting twice, on two different attributes. For most situations it's probably sufficient.

Polysyllable answered 27/3, 2013 at 18:55 Comment(2)
Thanks @amin-ariana, your solution worked for me and I think is more readable.Gunar
This behaves not as OP's initial code did for negative values like -5Trioecious
T
4

Just use value.to_i

starred.sort_by { |a| a.to_i }.reverse
Triphthong answered 5/10, 2017 at 12:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.