Сортировка массива строк в Ruby
Я изучил два метода сортировки массивов в Ruby:
array = ["one", "two", "three"]
array.sort.reverse!
или
array = ["one", "two", "three"]
array.sort { |x,y| y<=>x }
И я не могу отличить их. Какой метод лучше и насколько они отличаются при исполнении?
Ответы
Ответ 1
Обе строки делают то же самое (создайте новый массив, который отсортирован в обратном порядке). Основной аргумент - читаемость и производительность. array.sort.reverse!
более читаем, чем array.sort{|x,y| y<=>x}
. Думаю, мы сможем договориться здесь.
Для части производительности я создал быстрый тест script, который дает следующую информацию в моей системе (ruby 1.9.3p392 [x86_64-linux]
):
user system total real
array.sort.reverse 1.330000 0.000000 1.330000 ( 1.334667)
array.sort.reverse! 1.200000 0.000000 1.200000 ( 1.198232)
array.sort!.reverse! 1.200000 0.000000 1.200000 ( 1.199296)
array.sort{|x,y| y<=>x} 5.220000 0.000000 5.220000 ( 5.239487)
Время выполнения довольно постоянное для нескольких исполнений эталона script.
array.sort.reverse
(с или без !
) быстрее, чем array.sort{|x,y| y<=>x}
. Поэтому я рекомендую это.
Вот script как ссылка:
#!/usr/bin/env ruby
require 'benchmark'
Benchmark.bm do|b|
master = (1..1_000_000).map(&:to_s).shuffle
a = master.dup
b.report("array.sort.reverse ") do
a.sort.reverse
end
a = master.dup
b.report("array.sort.reverse! ") do
a.sort.reverse!
end
a = master.dup
b.report("array.sort!.reverse! ") do
a.sort!.reverse!
end
a = master.dup
b.report("array.sort{|x,y| y<=>x} ") do
a.sort{|x,y| y<=>x}
end
end
Ответ 2
Здесь нет никакой разницы. Оба метода возвращают новый массив.
Для целей этого примера проще. Я бы рекомендовал array.sort.reverse
, потому что он гораздо читабельнее, чем альтернатива. Передача блоков методам типа sort
должна сохраняться для массивов более сложных структур данных и пользовательских классов.
Изменить: в то время как методы destructive
(все, что заканчивается на a!) хороши для игр с производительностью, было указано, что они не являются обязательными, чтобы возвращать обновленный массив или что-либо вообще в этом отношении. Важно помнить об этом, потому что array.sort.reverse!
может с большой вероятностью вернуться nil
. Если вы хотите использовать деструктивный метод для вновь созданного массива, вы должны предпочесть вызов .reverse!
на отдельной строке вместо однострочного.
Пример:
array = array.sort
array.reverse!
следует предпочесть
array = array.sort.reverse!
Ответ 3
Reverse! быстрее
Часто нет замены для бенчмаркинга. Хотя это, вероятно, не имеет никакого значения в более коротких сценариях, #reverse! метод значительно быстрее, чем сортировка с использованием оператора "космического корабля". Например, на MRI Ruby 2.0 и с учетом следующего эталонного кода:
require 'benchmark'
array = ["one", "two", "three"]
loops = 1_000_000
Benchmark.bmbm do |bm|
bm.report('reverse!') { loops.times {array.sort.reverse!} }
bm.report('spaceship') { loops.times {array.sort {|x,y| y<=>x} }}
end
система сообщает, что #reverse! почти в два раза быстрее, чем при использовании комбинированного оператора сравнения.
user system total real
reverse! 0.340000 0.000000 0.340000 ( 0.344198)
spaceship 0.590000 0.010000 0.600000 ( 0.595747)
Мой совет: используйте то, что более семантически значимо в данном контексте, если вы не работаете в узком цикле.
Ответ 4
Сравнивая это просто, как ваш пример, нет большой разницы, но по мере усложнения формулы для сравнения лучше избегать использования <=>
с блоком, потому что пройденный вами блок будет оцениваться для каждого элемента массив, вызывая избыточность. Рассмотрим это:
array.sort{|x, y| some_expensive_method(x) <=> some_expensive_method(y)}
В этом случае some_expensive_method
будет оцениваться для каждой возможной пары элементов из array
.
В вашем конкретном случае использование блока с <=>
можно избежать с помощью reverse
.
array.sort_by{|x| some_expensive_method(x)}.reverse
Это называется преобразованием Шварца.
Ответ 5
В играх с тест-тестами tessi на моей машине у меня есть интересные результаты. Я запускаю ruby 2.0.0p195 [x86_64-darwin12.3.0]
, т.е. Последнюю версию Ruby 2 в системе OS X. Я использовал bmbm, а не bm
из модуля Benchmark. Мои тайминги:
Rehearsal -------------------------------------------------------------
array.sort.reverse: 1.010000 0.000000 1.010000 ( 1.020397)
array.sort.reverse!: 0.810000 0.000000 0.810000 ( 0.808368)
array.sort!.reverse!: 0.800000 0.010000 0.810000 ( 0.809666)
array.sort{|x,y| y<=>x}: 0.300000 0.000000 0.300000 ( 0.291002)
array.sort!{|x,y| y<=>x}: 0.100000 0.000000 0.100000 ( 0.105345)
---------------------------------------------------- total: 3.030000sec
user system total real
array.sort.reverse: 0.210000 0.000000 0.210000 ( 0.208378)
array.sort.reverse!: 0.030000 0.000000 0.030000 ( 0.027746)
array.sort!.reverse!: 0.020000 0.000000 0.020000 ( 0.020082)
array.sort{|x,y| y<=>x}: 0.110000 0.000000 0.110000 ( 0.107065)
array.sort!{|x,y| y<=>x}: 0.110000 0.000000 0.110000 ( 0.105359)
Во-первых, обратите внимание, что на этапе репетиции, когда sort!
с использованием блока сравнения входит в качестве явного победителя. Matz, должно быть, настроил heck из него в Ruby 2!
Другая вещь, которую я обнаружил чрезвычайно странно, заключалась в том, насколько значительное улучшение array.sort.reverse!
и array.sort!.reverse!
проявилось в прохождении производства. Это было настолько экстремально, что заставило меня задуматься, как-то я прищурился и передал эти уже отсортированные данные, поэтому я добавил явные проверки для отсортированных или отсортированных по ребрам данных до выполнения каждого теста.
Мой вариант tessi script следует:
#!/usr/bin/env ruby
require 'benchmark'
class Array
def sorted?
(1...length).each {|i| return false if self[i] < self[i-1] }
true
end
def reversed?
(1...length).each {|i| return false if self[i] > self[i-1] }
true
end
end
master = (1..1_000_000).map(&:to_s).shuffle
Benchmark.bmbm(25) do|b|
a = master.dup
puts "uh-oh!" if a.sorted?
puts "oh-uh!" if a.reversed?
b.report("array.sort.reverse:") { a.sort.reverse }
a = master.dup
puts "uh-oh!" if a.sorted?
puts "oh-uh!" if a.reversed?
b.report("array.sort.reverse!:") { a.sort.reverse! }
a = master.dup
puts "uh-oh!" if a.sorted?
puts "oh-uh!" if a.reversed?
b.report("array.sort!.reverse!:") { a.sort!.reverse! }
a = master.dup
puts "uh-oh!" if a.sorted?
puts "oh-uh!" if a.reversed?
b.report("array.sort{|x,y| y<=>x}:") { a.sort{|x,y| y<=>x} }
a = master.dup
puts "uh-oh!" if a.sorted?
puts "oh-uh!" if a.reversed?
b.report("array.sort!{|x,y| y<=>x}:") { a.sort!{|x,y| y<=>x} }
end