tags:

views:

781

answers:

4

I'd really like to handle this without monkey-patching but I haven't been able to find another option yet.

I have an array (in Ruby) that I need to sort by multiple conditions. I know how to use the sort method and I've used the trick on sorting using an array of options to sort by multiple conditions. However, in this case I need the first condition to sort ascending and the second to sort descending. For example:

ordered_list = [[1, 2], [1, 1], [2, 1]]

Any suggestions?

Edit: Just realized I should mention that I can't easily compare the first and second values (I'm actually working with object attributes here). So for a simple example it's more like:

ordered_list = [[1, "b"], [1, "a"], [2, "a"]]
+9  A: 

How about:


ordered_list = [[1, "b"], [1, "a"], [2, "a"]]
ordered_list.sort! do |a,b|
  [a[0],b[1]] <=> [b[0], a[1]]
end

Brian Phillips
Wonderful! Should have thought of that, knew I was missing something! Thanks!
PJ
That is RAD! Time to file that away in my ruby tricks book! Kudos!
Matt Rogish
A: 

I had this same basic problem, and solved it by adding this:

class Inverter
  attr_reader :o

  def initialize(o)
    @o = o
  end

  def <=>(other)
    if @o.is && other.o.is
      -(@o <=> other.o)
    else
      @o <=> other.o
    end
  end
end

This is a wrapper that simply inverts the <=> function, which then allows you to do things like this:

your_objects.sort_by {|y| [y.prop1,Inverter.new(y.prop2)]}
glenn mcdonald
+3  A: 

Enumerable#multisort is a generic solution that can be applied to arrays of any size, not just those with 2 items. Arguments are booleans that indicate whether a specific field should be sorted ascending or descending (usage below):

items = [
  [3, "Britney"],
  [1, "Corin"],
  [2, "Cody"],
  [5, "Adam"],
  [1, "Sally"],
  [2, "Zack"],
  [5, "Betty"]
]

module Enumerable
  def multisort(*args)
    sort do |a, b|
      i, res = -1, 0
      res = a[i] <=> b[i] until !res.zero? or (i+=1) == a.size
      args[i] == false ? -res : res
    end
  end
end

items.multisort(true, false)
# => [[1, "Sally"], [1, "Corin"], [2, "Zack"], [2, "Cody"], [3, "Britney"], [5, "Betty"], [5, "Adam"]]
items.multisort(false, true)
# => [[5, "Adam"], [5, "Betty"], [3, "Britney"], [2, "Cody"], [2, "Zack"], [1, "Corin"], [1, "Sally"]]
mislav
Neat! Thanks, that'll certainly come in handy later.
PJ
+1  A: 

Hi folks

I've been using Glenn's recipe for quite a while now. Tired of copying code from project to project over and over again, I've decided to make it a gem:

http://github.com/dadooda/invert

dadooda
Hey, that's very cool!
glenn mcdonald