#ruby #comparison #comparison-operators
#ruby #сравнение #сравнение-операторы
Вопрос:
Ruby поставляется с удобными <=>
операторами сравнения, и их поддерживают собственные примитивные типы. Мне интересно, есть ли простой способ объединить их для сравнения более сложных объектов, таких как структуры.Например, учитывая
class Datum < Struct.new(:code, :volume); end
datum1 = Datum.new('B',10)
datum2 = Datum.new('A',10)
datum3 = Datum.new('C',11)
data = [datum1, datum2, datum3]
Я хотел бы отсортировать data
по volume
, а затем, если volume
s равны, по code
. Нравится
data.sort {|a,b| (a.volume <=> b.volume) ??? (a.code <=> b.code)}
Что я должен вставить ???
?
Я ищу решение, которое:
- позволяет избежать пересчета
<=>
- Один вкладыш
- короткий 😉
Ответ №1:
Для такого простого случая, как описано выше, вы можете использовать sort_by
:
data.sort_by {|a| [a.volume, a.code] }
#=> [
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
Если вы сортируете только по одному атрибуту, он становится еще короче:
data.sort_by(amp;:volume)
#=> [
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
где amp;:volume
используется Symbol#to_proc
и является сокращением для proc {|a| a.volume }
(аналогично lambda).
Если вам нужно сделать его более сложным (т. Е. Иметь разные левую и правую стороны), вы можете расширить это до вызова sort
:
data.sort {|a,b| [a.volume, a.code] <=> [b.volume, b.code] }
#=> [
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
Все это работает, потому <=>
что оператор, определенный на Array
, делает именно то, что вам нужно, для произвольных уровней.
Комментарии:
1. Я попросил однострочный вариант и
data.sort {|a,b| [a.volume, a.code] <=> [b.volume, b.code] }
, конечно же, спасибо @p11y!
Ответ №2:
Я думаю, вы идете по этому неправильному пути. Почему бы вам не избавить себя от хлопот и не добавить <=>
оператор в Datum
? Что-то вроде этого:
class Datum < Struct.new(:code, :volume)
def <=>(other)
if(self.code < other.code)
-1
elsif(self.code > other.code)
1
elsif(self.volume < other.volume)
-1
elsif(self.volume > other.volume)
1
else
0
end
end
end
Тогда вы можете сказать data.sort
и покончить с этим. И как только у вас есть <=>
оператор, вы можете include Comparable
и получить <
, <=
, … операторы бесплатно.
Ответ №3:
Способ Ruby для объединения компараторов в цепочку заключается в использовании Numeric.nonzero?
:
data.sort {|a,b| (a.volume <=> b.volume).nonzero? || a.code <=> b.code}
Что Numeric.nonzero?
делать? Он возвращает число в случае, если оно отличается от нуля, в противном случае оно возвращается nil
. Затем ||
оператор заботится обо всем остальном, поскольку nil
обрабатывается как false
в логическом контексте, таким образом nil || b
, является таким же, как b
.
При этом в большинстве ситуаций вы получите более короткий код при использовании Enumerable.sort_by
и вернете массив для указания порядка сортировки:
data.sort_by {|a| [a.volume, a.code] }
Также проще изменить порядок сортировки, чем при использовании компаратора:
data.sort {|a,b| (-(a.volume <=> b.volume)).nonzero? || a.code <=> b.code}
# vs.
data.sort_by {|a| [-a.volume, a.code] }
Внимание: имейте в виду, что sort_by
это медленнее, чем sort
в случае, если компаратор работает быстро, и, наоборот, sort_by
лучше, если компаратор работает медленно.