Подтвердить что ты не робот

Сортировка массива строк в Ruby

Я изучил два метода сортировки массивов в Ruby:

array = ["one", "two", "three"]
array.sort.reverse!

или

array = ["one", "two", "three"]
array.sort { |x,y| y<=>x }

И я не могу отличить их. Какой метод лучше и насколько они отличаются при исполнении?

4b9b3361

Ответ 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