Сортировка нескольких значений по возрастанию и убыванию

Я пытаюсь сортировать массив объектов на основе разных атрибутов. Некоторые из этих атрибутов я хотел бы сортировать в порядке возрастания, а некоторые - в порядке убывания. Я смог сортировать по восходящей или нисходящей, но не смог совместить эти два.

Вот простой класс, с которым я работаю:

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

Затем я могу создать несколько собак для сортировки позже.

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

Затем я могу использовать метод sort_all_by_gender_then_name.

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

В массив, который он возвращает, входят самки сначала, затем самцы, все отсортированные по имени в порядке возрастания.

Но что, если я хочу, чтобы гендер спустился, а затем назовите восхождение, чтобы сначала были мужчины, а затем отсортированы по имени по возрастанию. В этом случае:

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

Или, если бы я хотел его по полу по возрастанию, но имя спускалось:

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

При сортировке числовых значений вы можете добавить a -, чтобы сделать сортировку в обратном порядке. Однако я не смог найти способ сделать это со строками. Любая помощь или идеи будут оценены. Благодарю.

Ответ 1

Здесь один из способов сделать это, используя .sort вместо .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)

Вывод:

[{: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"}]

В принципе, это делает что-то похожее на отрицание чисел (как вы упомянули в вопросе), путем замены свойства на другой элемент, если его нужно сортировать в порядке убывания.

Ответ 2

Этот ReversedOrder ReversedOrder может помочь вам выполнить сортировку смешанного направления по отдельным атрибутам, используя sort_by:

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

Пример использования:

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"}]

Примечание: Будьте осторожны, чтобы dup обращенного элемента. Без этого вы будете смешивать инвертор сравнения с фактическим объектом, а не только с ключом, созданным для sort_by и он навсегда будет производить обратные сравнения.