Как отсортировать массив в порядке убывания в Ruby

282

У меня есть массив хэшей:

[
  { :foo => 'foo', :bar => 2 },
  { :foo => 'foo', :bar => 3 },
  { :foo => 'foo', :bar => 5 },
]

Я пытаюсь отсортировать этот массив в порядке убывания в соответствии со значением :barв каждом хеше.

Я использую sort_byдля сортировки выше массив:

a.sort_by { |h| h[:bar] }

Однако это сортирует массив в порядке возрастания. Как мне сделать сортировку в порядке убывания?

Одним из решений было сделать следующее:

a.sort_by { |h| -h[:bar] }

Но этот отрицательный знак не кажется уместным.

Waseem
источник
4
Учитывая другие варианты, я все еще думаю, что -h [: bar] - самый элегантный. Что тебе не нравится в этом?
Майкл Коль
2
Мне было гораздо интереснее передать смысл кода.
Васим
1
@ Васим, могу ли я побеспокоить вас обновить принятый ответ?
Коллин
7
@ Waseem Нет ничего плохого в текущем ответе. Там просто бывает гораздо лучший ответ. ответ «Жестянщика» гораздо более тщательный и показывает, что sort_by.reverseон значительно более эффективен, чем принятый в настоящее время ответ. Я полагаю, что это также лучше решает проблему, о которой вы упоминали выше, «передавая намерение кода». Кроме того, Железный Человек обновил свой ответ для текущей версии ruby. Этот вопрос был просмотрен более 15 раз. Если вы можете сэкономить хотя бы 1 секунду времени каждого зрителя, я думаю, это того стоит.
Коллин
3
@Collindo Спасибо, я сделал это. :)
Waseem

Ответы:

566

Всегда полезно сделать оценку различных предложенных ответов. Вот что я узнал:

#! / USR / бен / рубин

требует «эталон»

ary = []
1000.times { 
  ary << {: bar => rand (1000)} 
}

n = 500
Benchmark.bm (20) do | x |
  x.report ("sort") {n.times {ary.sort {| a, b | b [: bar] <=> a [: bar]}}}
  x.report ("сортировка в обратном порядке") {n.times {ary.sort {| a, b | a [: bar] <=> b [: bar]} .reverse}}
  x.report ("sort_by -a [: bar]") {n.times {ary.sort_by {| a | -бар] } } }
  x.report ("sort_by a [: bar] * - 1") {n.times {ary.sort_by {| a | a [: bar] * - 1}}}
  x.report ("sort_by.reverse!") {n.times {ary.sort_by {| a | a [: bar]} .reverse}}
конец

                          пользовательская система всего реального
сортировка 3.960000 0.010000 3.970000 (3.990886)
обратная сортировка 4.040000 0.000000 4.040000 (4.038849)
sort_by -a [: bar] 0.690000 0.000000 0.690000 (0.692080)
sort_by a [: bar] * - 1 0,700000 0,000000 0,700000 (0,699735)
sort_by.reverse! 0,650000 0,000000 0,650000 (0,654447)

Я думаю, интересно, что @ Pablo's sort_by{...}.reverse!самый быстрый. Перед запуском теста я подумал, что это будет медленнее, чем " -a[:bar]", но отрицание значения оказывается дольше, чем обратное преобразование всего массива за один проход. Это не большая разница, но каждое небольшое ускорение помогает.


Обратите внимание, что эти результаты отличаются в Ruby 1.9

Вот результаты для Ruby 1.9.3p194 (2012-04-20 ревизия 35410) [x86_64-darwin10.8.0]:

                           user     system      total        real
sort                   1.340000   0.010000   1.350000 (  1.346331)
sort reverse           1.300000   0.000000   1.300000 (  1.310446)
sort_by -a[:bar]       0.430000   0.000000   0.430000 (  0.429606)
sort_by a[:bar]*-1     0.420000   0.000000   0.420000 (  0.414383)
sort_by.reverse!       0.400000   0.000000   0.400000 (  0.401275)

Это на старом MacBook Pro. Более новые или более быстрые машины будут иметь более низкие значения, но относительные различия останутся.


Вот немного обновленная версия на более новом оборудовании и версии Ruby 2.1.1:

#!/usr/bin/ruby

require 'benchmark'

puts "Running Ruby #{RUBY_VERSION}"

ary = []
1000.times {
  ary << {:bar => rand(1000)}
}

n = 500

puts "n=#{n}"
Benchmark.bm(20) do |x|
  x.report("sort")               { n.times { ary.dup.sort{ |a,b| b[:bar] <=> a[:bar] } } }
  x.report("sort reverse")       { n.times { ary.dup.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse } }
  x.report("sort_by -a[:bar]")   { n.times { ary.dup.sort_by{ |a| -a[:bar] } } }
  x.report("sort_by a[:bar]*-1") { n.times { ary.dup.sort_by{ |a| a[:bar]*-1 } } }
  x.report("sort_by.reverse")    { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse } }
  x.report("sort_by.reverse!")   { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse! } }
end

# >> Running Ruby 2.1.1
# >> n=500
# >>                            user     system      total        real
# >> sort                   0.670000   0.000000   0.670000 (  0.667754)
# >> sort reverse           0.650000   0.000000   0.650000 (  0.655582)
# >> sort_by -a[:bar]       0.260000   0.010000   0.270000 (  0.255919)
# >> sort_by a[:bar]*-1     0.250000   0.000000   0.250000 (  0.258924)
# >> sort_by.reverse        0.250000   0.000000   0.250000 (  0.245179)
# >> sort_by.reverse!       0.240000   0.000000   0.240000 (  0.242340)

Новые результаты запуска вышеуказанного кода с использованием Ruby 2.2.1 на более позднем Macbook Pro. Опять же, точные цифры не важны, это их отношения:

Running Ruby 2.2.1
n=500
                           user     system      total        real
sort                   0.650000   0.000000   0.650000 (  0.653191)
sort reverse           0.650000   0.000000   0.650000 (  0.648761)
sort_by -a[:bar]       0.240000   0.010000   0.250000 (  0.245193)
sort_by a[:bar]*-1     0.240000   0.000000   0.240000 (  0.240541)
sort_by.reverse        0.230000   0.000000   0.230000 (  0.228571)
sort_by.reverse!       0.230000   0.000000   0.230000 (  0.230040)

Обновлен для Ruby 2.7.1 на MacBook Pro середины 2015 года:

Running Ruby 2.7.1
n=500     
                           user     system      total        real
sort                   0.494707   0.003662   0.498369 (  0.501064)
sort reverse           0.480181   0.005186   0.485367 (  0.487972)
sort_by -a[:bar]       0.121521   0.003781   0.125302 (  0.126557)
sort_by a[:bar]*-1     0.115097   0.003931   0.119028 (  0.122991)
sort_by.reverse        0.110459   0.003414   0.113873 (  0.114443)
sort_by.reverse!       0.108997   0.001631   0.110628 (  0.111532)

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

Источник для Array#reverse:

               static VALUE
rb_ary_reverse_m(VALUE ary)
{
    long len = RARRAY_LEN(ary);
    VALUE dup = rb_ary_new2(len);

    if (len > 0) {
        const VALUE *p1 = RARRAY_CONST_PTR_TRANSIENT(ary);
        VALUE *p2 = (VALUE *)RARRAY_CONST_PTR_TRANSIENT(dup) + len - 1;
        do *p2-- = *p1++; while (--len > 0);
    }
    ARY_SET_LEN(dup, RARRAY_LEN(ary));
    return dup;
}

do *p2-- = *p1++; while (--len > 0); копирует указатели на элементы в обратном порядке, если я правильно помню свой C, поэтому массив перевернут.

жестяной человек
источник
45
Супер полезно. Спасибо за дополнительные усилия.
Джошуа Пинтер
7
я люблю, когда люди предоставляют эталонное доказательство как это !! Потрясающие!
ktec
25
«Мне нравится, когда люди предоставляют эталонное доказательство, как это !!» Я тоже, потому что тогда мне не нужно.
Жестянщик
9
@theTinMan Можете ли вы предоставить TL; DR для вашего ответа. Вся эта информация о тестах довольно полезна. Но TL; DR поверх ответа будет полезен людям, которые просто хотят получить ответ. Я знаю, что они должны прочитать все объяснения, и я думаю, что они будут. Все-таки TL; DR будет весьма полезен ИМХО. Спасибо за ваши усилия.
Васим
8
Я согласен с @Waseem. Несмотря на то, что этот ответ хорошо изучен, OP не спросил, «какой самый быстрый способ сделать сортировку по убыванию в Ruby». TL; DR вверху, показывающий простое использование, сопровождаемое эталонами, улучшило бы этот ответ IMO.
89

Просто быстрая вещь, которая обозначает намерение по убыванию.

descending = -1
a.sort_by { |h| h[:bar] * descending }

(Пока подумаем о лучшем способе);)


a.sort_by { |h| h[:bar] }.reverse!
Пабло Фернандес
источник
Пабло, хорошая работа по поиску лучшего пути! Смотрите тест, который я сделал.
Жестянщик
Первый метод быстрее (хотя, может быть, и более уродливый), потому что он повторяется только один раз. Что касается второго, вам не нужен!, Это для операций на месте.
Tokland
3
Если вы не используете удар после реверса, вы не будете переворачивать массив, а создаете другой, который перевернут.
Пабло Фернандес
56

Вы могли бы сделать:

a.sort{|a,b| b[:bar] <=> a[:bar]}
JRL
источник
4
но весь смысл использования sort_byбыл в том, что он избегал многократного запуска функции сравнения
user102008
3
-1. sort_byгораздо эффективнее и читабельнее. Отрицание значения или обратное в конце будет быстрее и более читабельным.
Марк-Андре Лафортун
1
Мне нравится этот ответ, потому что * -1он не работает со всеми значениями (например, временем) и reverseбудет переупорядочивать значения, отсортированные как равные
Abe Voelker
8

Я вижу, что у нас (помимо других) в основном два варианта:

a.sort_by { |h| -h[:bar] }

и

a.sort_by { |h| h[:bar] }.reverse

Хотя оба способа дают один и тот же результат, когда ваш ключ сортировки уникален, имейте в виду, что этот reverseспособ обратит порядок ключей, которые равны .

Пример:

a = [{foo: 1, bar: 1},{foo: 2,bar: 1}]
a.sort_by {|h| -h[:bar]}
 => [{:foo=>1, :bar=>1}, {:foo=>2, :bar=>1}]
a.sort_by {|h| h[:bar]}.reverse
 => [{:foo=>2, :bar=>1}, {:foo=>1, :bar=>1}]

Хотя вам часто не нужно заботиться об этом, иногда вам это нужно. Чтобы избежать такого поведения, вы можете ввести второй ключ сортировки (который наверняка должен быть уникальным по крайней мере для всех элементов, имеющих одинаковый ключ сортировки):

a.sort_by {|h| [-h[:bar],-h[:foo]]}
 => [{:foo=>2, :bar=>1}, {:foo=>1, :bar=>1}]
a.sort_by {|h| [h[:bar],h[:foo]]}.reverse
 => [{:foo=>2, :bar=>1}, {:foo=>1, :bar=>1}]
Никлас Хесл
источник
+1 за указание, что семантика reverseотличается. Я полагаю, что это также испортит предыдущий вид, в случае, когда кто-то пытается применить несколько видов в некотором порядке.
Джонсип
6

Что о:

 a.sort {|x,y| y[:bar]<=>x[:bar]}

Оно работает!!

irb
>> a = [
?>   { :foo => 'foo', :bar => 2 },
?>   { :foo => 'foo', :bar => 3 },
?>   { :foo => 'foo', :bar => 5 },
?> ]
=> [{:bar=>2, :foo=>"foo"}, {:bar=>3, :foo=>"foo"}, {:bar=>5, :foo=>"foo"}]

>>  a.sort {|x,y| y[:bar]<=>x[:bar]}
=> [{:bar=>5, :foo=>"foo"}, {:bar=>3, :foo=>"foo"}, {:bar=>2, :foo=>"foo"}]
OscarRyz
источник
Да, это действительно работает, но я думаю, что ПО хочет показать намерение с кодом (у него уже есть рабочее решение).
Пабло Фернандес
Хотя sortбудет работать, это только быстрее при сортировке непосредственных значений. Если вам нужно копать их sort_byбыстрее. Смотрите тест.
Оловянный Человек
3

Что касается упомянутого набора тестов, эти результаты также применимы для отсортированных массивов.

sort_by/ reverseэто:

# foo.rb
require 'benchmark'

NUM_RUNS = 1000

# arr = []
arr1 = 3000.times.map { { num: rand(1000) } }
arr2 = 3000.times.map { |n| { num: n } }.reverse

Benchmark.bm(20) do |x|
  { 'randomized'     => arr1,
    'sorted'         => arr2 }.each do |label, arr|
    puts '---------------------------------------------------'
    puts label

    x.report('sort_by / reverse') {
      NUM_RUNS.times { arr.sort_by { |h| h[:num] }.reverse }
    }
    x.report('sort_by -') {
      NUM_RUNS.times { arr.sort_by { |h| -h[:num] } }
    }
  end
end

И результаты:

$: ruby foo.rb
                           user     system      total        real
---------------------------------------------------
randomized
sort_by / reverse      1.680000   0.010000   1.690000 (  1.682051)
sort_by -              1.830000   0.000000   1.830000 (  1.830359)
---------------------------------------------------
sorted
sort_by / reverse      0.400000   0.000000   0.400000 (  0.402990)
sort_by -              0.500000   0.000000   0.500000 (  0.499350)
Кристофер Куттруфф
источник
Вы должны быть в состоянии сделать sort_by {}. (обратный без взрыва создает новый массив, и я, конечно, ожидал, что он будет медленнее)
bibstha
2

Простое решение от восходящего до нисходящего и наоборот:

НИТИ

str = ['ravi', 'aravind', 'joker', 'poker']
asc_string = str.sort # => ["aravind", "joker", "poker", "ravi"]
asc_string.reverse # => ["ravi", "poker", "joker", "aravind"]

ЦИФРЫ

digit = [234,45,1,5,78,45,34,9]
asc_digit = digit.sort # => [1, 5, 9, 34, 45, 45, 78, 234]
asc_digit.reverse # => [234, 78, 45, 45, 34, 9, 5, 1]
Ravistm
источник
1

Для тех, кто любит измерять скорость в IPS;)

require 'benchmark/ips'

ary = []
1000.times { 
  ary << {:bar => rand(1000)} 
}

Benchmark.ips do |x|
  x.report("sort")               { ary.sort{ |a,b| b[:bar] <=> a[:bar] } }
  x.report("sort reverse")       { ary.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse }
  x.report("sort_by -a[:bar]")   { ary.sort_by{ |a| -a[:bar] } }
  x.report("sort_by a[:bar]*-1") { ary.sort_by{ |a| a[:bar]*-1 } }
  x.report("sort_by.reverse!")   { ary.sort_by{ |a| a[:bar] }.reverse }
  x.compare!
end

И результаты:

Warming up --------------------------------------
                sort    93.000  i/100ms
        sort reverse    91.000  i/100ms
    sort_by -a[:bar]   382.000  i/100ms
  sort_by a[:bar]*-1   398.000  i/100ms
    sort_by.reverse!   397.000  i/100ms
Calculating -------------------------------------
                sort    938.530   1.8%) i/s -      4.743k in   5.055290s
        sort reverse    901.157   6.1%) i/s -      4.550k in   5.075351s
    sort_by -a[:bar]      3.814k  4.4%) i/s -     19.100k in   5.019260s
  sort_by a[:bar]*-1      3.732k  4.3%) i/s -     18.706k in   5.021720s
    sort_by.reverse!      3.928k  3.6%) i/s -     19.850k in   5.060202s

Comparison:
    sort_by.reverse!:     3927.8 i/s
    sort_by -a[:bar]:     3813.9 i/s - same-ish: difference falls within error
  sort_by a[:bar]*-1:     3732.3 i/s - same-ish: difference falls within error
                sort:      938.5 i/s - 4.19x  slower
        sort reverse:      901.2 i/s - 4.36x  slower
mpospelov
источник