2

Is there any way to use sort_by and make nils appear in the front.

For example

[-1, 2, 3, nil, nil].sort_by &some_block

should give

#=> [nil, nil, -1, 2, 3]

It's similar to this question but the solution there does not work with negative values.

Community
  • 1
  • 1
Lokesh
  • 2,842
  • 7
  • 32
  • 47
  • 1
    _Sidenote_: the accepted answer to the linked question perfectly works for negatives, even more, the answer by John is an exact copy of it. – Aleksei Matiushkin Mar 02 '16 at 10:29
  • 1
    @mudasobwa My bad! I am sorry. The answer is working for negatives. I don't how I missed that. – Lokesh Mar 02 '16 at 10:48
  • @Stefan, I am not convinced this is a duplicate of the referenced question. Even if it were, however, the (two) answers there do not span the range of possible approaches to this problem. Aside: I find the latter is often a problem when marking a question as a dup. Sometimes the referenced question has no good answers. – Cary Swoveland Mar 02 '16 at 17:04
  • @mudasobwa, nearly. I am explicitly not treating `false` the same as `nil`, but there's not much point in since booleans can't be compared in Ruby anyway. – John La Rooy Mar 02 '16 at 19:43

3 Answers3

3

You can use Float::INFINITY if your other values are numeric:

[-1, 2, 3, nil, nil].sort_by { |n| n || -Float::INFINITY }
#=> [nil, nil, -1, 2, 3]

Another way to write this is:

sort_by { |n| n ? n : -Float::INFINITY }

or more explicitly regarding nil:

sort_by { |n| n.nil? ? -Float::INFINITY : n }
Stefan
  • 109,145
  • 14
  • 143
  • 218
3
> [-1, 2, 3, nil, nil].sort_by{|x| [(x.nil?)?0:1, x]}
 => [nil, nil, -1, 2, 3] 

This avoids comparing int with nil, by takign advantage of the short circuiting behaviour of the <=> for arrays

John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • I understand how `x.nil? 0:1` makes it appear before positives but don't understand how placing x at the end in `[(x.nil?)?0:1, x]` makes it appear at the front before negative numbers? – Lokesh Mar 02 '16 at 10:53
  • @Lokesh arrays are compared element-wise, i.e. `[0, x]` comes before `[1, y]` regardless of `x` and `y`. See [`Array#<=>`](http://ruby-doc.org/core-2.3.0/Array.html#method-i-3C-3D-3E) – Stefan Mar 02 '16 at 10:56
  • Ok. Thanks. Can you help me decide which way is better performance wise. This another or yours @Stefan? – Lokesh Mar 02 '16 at 10:57
  • 1
    Comparing numerics is undoubtedly faster than comparing arrays. – Aleksei Matiushkin Mar 02 '16 at 10:58
0

I suggest

def sort_by_with_val_first(arr, val=nil)
  ([val]*arr.count(val)).concat (arr-[val]).sort_by { |e| yield e }
end

arr = [-1, 2, 3, nil, nil, -4]
sort_by_with_val_first(arr) { |x| x.abs }
  #=> [nil, nil, -1, 2, 3, -4]

I like how this reads: "Create an array comprised of the elements val in arr, then concatenate this array with arr with elements val removed, sorted as desired". Converting val in the sort_by to an artificial value that will make it work is, to me, aesthetically displeasing.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100