Question

I have an array of objects that I need to sort by a position attribute that could be an integer or nil, and I need the objects that have the nil position to be at the end of the array. Now, I can force the position to return some value rather than nil so that the array.sort doesn't fail, but if I use 0 as this default, then it puts those objects at the front of the sort. What's the best way to to do this sort? should I just set the nil values to some ridiculously high number that is 'almost' always guaranteed to be at the end? or is there some other way i could cause the array.sort method to put the nil attribute objects at the end of the array? the code looks like this:

class Parent
  def sorted_children
     children.sort{|a, b| a.position <=> b.position}
  end
end

class Child
  def position
    category ? category.position : #what should the else be??
  end
end

now, if i make the 'else' something like 1000000000, then it's most likely gonna put them at the end of the array, but I don't like this solution as it's arbitrary

Was it helpful?

Solution

How about in Child defining <=> to be based on category.position if category exists, and sorting items without a category as always greater than those with a category?

class Child
  # Not strictly necessary, but will define other comparisons based on <=>
  include Comparable   
  def <=> other
    return 0 if !category && !other.category
    return 1 if !category
    return -1 if !other.category
    category.position <=> other.category.position
  end
end

Then in Parent you can just call children.sort.

OTHER TIPS

I would just tweak your sort to put nil items last. Try something like this.

foo = [nil, -3, 100, 4, 6, nil, 4, nil, 23]

foo.sort { |a,b| a && b ? a <=> b : a ? -1 : 1 }

=> [-3, 4, 4, 6, 23, 100, nil, nil, nil]

That says: if a and b are both non-nil sort them normally but if one of them is nil, return a status that sorts that one larger.

I handle these kinds of things like this:

 children.sort_by {|child| [child.position ? 0 : 1,child.position || 0]}

To be fair, I'm not very familiar with Ruby, so take this as more of an algorithm idea rather than a code one... and rewrite the ?: operator as whatever Ruby has that's cleaner.

Can't you just check for nil in the comparison:

class Parent
  def sorted_children
     children.sort{|a,b|( a and b ) ? a <=> b : ( a ? -1 : 1 ) }
  end
end

Edited to use Glenra's code, which implements the same thing as mine but in a smaller (and probably easier to read) amount of code.

I haven't done Ruby in a while, but you could split the null-checking from the sorting (and just allow Child#position to return null):

def sorted_children
  children.reject{|c| c.position.nil?}.sort_by(&:position) +
    children.select{|c| c.position.nil?}
end

Admittedly it's not the most efficient solution, but it doesn't have any magic numbers.

You can do this without overriding the spaceship operator by defining a new comparison method.

class Child
  include Comparable   
  def compare_by_category(other)
    return 0 if !category && !other.category
    return 1 if !category
    return -1 if !other.category
    category.position <=> other.category.position
  end
end

The sort method can take a block, so you can then sort using this new method:

children.sort {|a,b| a.compare_by_category(b) }

The most simple solution for me is

def sorted_children(children)
  children.sort_by { |child| child.position || -1}
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top