1

How can I sort an array like the one below :

[["9213533",
  {:length=>"9213533",
   :units=>["Meters", "Feet", "Yards"],
   :frequency=>3}],
 [nil, 
  {:length=>nil, :units=>["Feet"], :frequency=>1}],
 ["5951975", 
  {:length=>"5951975", :units=>["Yards"], :frequency=>1}],
 ["9100799", 
  {:length=>"9100799", :units=>["Feet"], :frequency=>1}]]

I'd like to sort it so the first element of arrays array who is nil comes to the last place like this (sorted) :

[["9213533",
      {:length=>"9213533",
       :units=>["Meters", "Feet", "Yards"],
       :frequency=>3}],
     ["5951975", 
      {:length=>"5951975", :units=>["Yards"], :frequency=>1}],
     ["9100799", 
      {:length=>"9100799", :units=>["Feet"], :frequency=>1}]],
     [nil, 
      {:length=>nil, :units=>["Feet"], :frequency=>1}]

I've tried with sort by method (arr is my array):

arr.sort_by{|a,b| b[:length] unless a.nil?}

Got this exception :

ArgumentError: comparison of NilClass with String failed
from (pry):395:in `sort_by
2
  • is a always equal to b[:length] as in your example? Commented Jun 26, 2013 at 13:30
  • 1
    Your code suggests you are trying to sort also by length but the expected output you paste is not order by length, can you please clarify? Commented Jun 26, 2013 at 15:13

3 Answers 3

6
arr2 = arr.sort_by { |k, h| k ? [0, h[:length].to_i] : [1] }

Or:

arr2 = arr.sort_by { |k, h| k ? h[:length].to_i : Float::INFINITY }

[Edit] That's what I thought you were asking from your code, but in fact you ask to put the first nil in last place, that's a completely different thing:

idx = arr.index { |k, h| k.nil? }
arr2 = idx ? [arr[0...idx] + arr[idx+1..-1], arr[idx]] : [arr, nil]
Sign up to request clarification or add additional context in comments.

8 Comments

Float::INFINITY produces NameError: uninitialized constant Object::Infinity on Ruby 2.0.0p0. I suggest using 1.0/0.0 instead.
@lolmaus: That's weird, it was introduced in 1.9, why would they remove it? more weird still, it works for me: 2.0.0p0 :001 > Float::INFINITY => Infinity
They didn't remove it, it just can't be used directly: bounga.org/ruby/2011/09/12/use-infinity
@lolmaus: it's available and documented in 2.0: ruby-doc.org/core-2.0/Float.html#INFINITY :-/
The problem is that I do not understand the expected output, it's not ordered by length at all, which contradicts the code the OP shows. Moreover no feedback for the moment... I'll ask.
|
3

All earlier answers based on sort or sort_by are wrong, as they modify the order of non-nil values. Note, that the asker wants only the nil values to be sunk to the end of the array, while preserving the original order of all other elements.

Here's how it can be done correctly.

Short version

class Array
  def sink_nils!
    self.replace self.partition { |value| value[0] != nil }.flatten(1)
  end
end

arr.sink_nils!

This code converts arr into an array equal to the one requested by the asker.

Same code in a more readable form, explained

class Array
  def sink_nils!
    # Move nil-containing elements into a separate array
    result = self.partition { |value| value[0] != nil }

    # Glue two arrays into a single one
    result.flatten!(1)

    # Swap the original array with the result
    self.replace result
  end
end

arr.sink_nils!

Here's what happening.

  • .partition splits the array into two arrays, based on the condition provided in a block. We use a value[0] != nil condition which separates nil-containing items from non-nil-containing ones, while preserving their original order.
  • As .partition returns an array of arrays, we glue it back together using .flatten. We don't want to lose the hierarchy of inner arrays, so we request only one level of flattening: .flatten(1).
  • The whole thing is declared as a method for the Array class, so that in can be conveniently used. As the method is destructive (it modifies the original array), we add an exclaimation mark to the end of its name: .sink_nils!.
  • Finally, the method is applied on the existing array.

3 Comments

It's not necessary to say all other answers are wrong. Show the output of your code, explain why yours is right, and let the OP decide.
I have explained why mine is right and why sort-based ones are wrong. And i have already said that the resulting array is identical to the one requested, there's no need to duplicate it.
well, now it's my turn ;-) "your answer is wrong as it produces an array different from the one desired by the asker": the first nil-element should be moved to the last element of the pair (the first element of the pair are the rest of the elements).
2

The sort_by block must return something that can always be compared. Adding the unless prevents it doing that (because the block has nothing else to return, and nil cannot be compared with a Fixnum)

Instead, you need a fallback result to use as a sort key when a is nil:

arr.sort_by{|a,b| a.nil? ? 999999999 : b[:length].to_i }

2 Comments

Your answer is wrong as it produces an array different from the one desired by the asker.
@lolmaus: Ah yes, well spotted. May not be an important detail to OP, we'll have to see. I'd add my answer is also lacking in that it is not generic when faced with arbitrary content of b[:length]. But it is simple.

Your Answer

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.