Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting multiple values by ascending and descending

Tags:

sorting

ruby

I'm trying to sort an array of objects based upon different attributes. Some of those attributes I would like to sort in ascending order and some in descending order. I have been able to sort by ascending or descending but have been unable to combine the two.

Here is the simple class I am working with:

class Dog
  attr_reader :name, :gender

  DOGS = []

  def initialize(name, gender)
    @name = name
    @gender = gender
    DOGS << self
  end

  def self.all
    DOGS
  end

  def self.sort_all_by_gender_then_name
    self.all.sort_by { |d| [d.gender, d.name] }
  end
end

I can then instantiate some dogs to be sorted later.

@rover = Dog.new("Rover", "Male")
@max = Dog.new("Max", "Male")
@fluffy = Dog.new("Fluffy", "Female")
@cocoa = Dog.new("Cocoa", "Female")

I can then use the sort_all_by_gender_then_name method.

Dog.sort_all_by_gender_then_name
=> [@cocoa, @fluffy, @max, @rover]

The array it returns includes females first, then males, all sorted by name in ascending order.

But what if I want to have gender be descending, and then name ascending, so that it would be males first and then sorted by name ascending. In this case:

=> [@max, @rover, @cocoa, @fluffy]

Or, if I wanted it by gender ascending, but name descending:

=> [@fluffy, @cocoa, @rover, @max]

When sorting numerical values, you can prepend a - to make it sort in reverse. However, I have been unable to find a way to do this with strings. Any help or ideas would be appreciated. Thanks.

like image 249
John K. Ferguson Avatar asked May 18 '13 20:05

John K. Ferguson


People also ask

How do you sort the elements in both ascending and descending order?

sort() method sorts the elements of a list in ascending or descending order using the default < comparisons operator between items. Use the key parameter to pass the function name to be used for comparison instead of the default < operator. Set the reverse parameter to True, to get the list in descending order.


2 Answers

Here's one way to do it using .sort instead of .sort_by:

dogs = [
  { name: "Rover", gender: "Male" },
  { name: "Max", gender: "Male" },
  { name: "Fluffy", gender: "Female" },
  { name: "Cocoa", gender: "Female" }
]

# gender asc, name asc
p(dogs.sort do |a, b|
  [a[:gender], a[:name]] <=> [b[:gender], b[:name]]
end)

# gender desc, name asc
p(dogs.sort do |a, b|
  [b[:gender], a[:name]] <=> [a[:gender], b[:name]]
end)

# gender asc, name desc
p(dogs.sort do |a, b|
  [a[:gender], b[:name]] <=> [b[:gender], a[:name]]
end)

Output:

[{:name=>"Cocoa", :gender=>"Female"}, {:name=>"Fluffy", :gender=>"Female"}, {:name=>"Max", :gender=>"Male"}, {:name=>"Rover", :gender=>"Male"}]
[{:name=>"Max", :gender=>"Male"}, {:name=>"Rover", :gender=>"Male"}, {:name=>"Cocoa", :gender=>"Female"}, {:name=>"Fluffy", :gender=>"Female"}]
[{:name=>"Fluffy", :gender=>"Female"}, {:name=>"Cocoa", :gender=>"Female"}, {:name=>"Rover", :gender=>"Male"}, {:name=>"Max", :gender=>"Male"}]

Basically, this is doing something similar to negating numbers (as you mentioned in the question), by swapping the property to the other element if it needs to be sorted in descending order.

like image 90
Dogbert Avatar answered Oct 13 '22 18:10

Dogbert


This ReversedOrder mixin can help you accomplish the mixed direction sorts on seperate attributes, using sort_by:

module ReversedOrder
  def <=>(other)
    - super
  end
end

Use example:

dogs = [
  { name: "Rover", gender: "Male" },
  { name: "Max", gender: "Male" },
  { name: "Fluffy", gender: "Female" },
  { name: "Cocoa", gender: "Female" }
]

dogs.sort_by {|e| [e[:gender], e[:name]] }
=> [{:name=>"Cocoa", :gender=>"Female"},
 {:name=>"Fluffy", :gender=>"Female"},
 {:name=>"Max", :gender=>"Male"},
 {:name=>"Rover", :gender=>"Male"}]

dogs.sort_by {|e| [e[:gender].dup.extend(ReversedOrder), e[:name]] }
=> [{:name=>"Max", :gender=>"Male"},
 {:name=>"Rover", :gender=>"Male"},
 {:name=>"Cocoa", :gender=>"Female"},
 {:name=>"Fluffy", :gender=>"Female"}]

Note: Be careful to dup the reversed element. Without that, you will mixin the comparison inverter to the actual object instead of just the key being made for sort_by and it will forever produce reversed comparisons.

like image 30
dbenhur Avatar answered Oct 13 '22 16:10

dbenhur