Jak posortować tablicę w porządku malejącym w Ruby

282

Mam szereg skrótów:

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

Próbuję posortować tę tablicę w kolejności malejącej zgodnie z wartością :barw każdym haszu.

Używam sort_bydo sortowania powyżej tablicy:

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

Jednak sortuje tablicę w kolejności rosnącej. Jak ustawić sortowanie w kolejności malejącej?

Jednym z rozwiązań było wykonanie następujących czynności:

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

Ale ten znak negatywny nie wydaje się odpowiedni.

Waseem
źródło
4
Biorąc pod uwagę inne opcje, nadal uważam, że -h [: bar] jest najbardziej elegancki. Co ci się w tym nie podoba?
Michael Kohl,
2
Byłem znacznie bardziej zainteresowany przekazaniem zamiaru kodu.
Waseem
1
@Waseem czy mogę sprawić ci kłopot z aktualizacją zaakceptowanej odpowiedzi?
colllin
7
@Waseem Nie ma nic złego w obecnej odpowiedzi. Tak się składa, że ​​jest o wiele lepsza odpowiedź. odpowiedź Tin Mana jest znacznie dokładniejsza i pokazuje, że sort_by.reversejest znacznie bardziej wydajna niż obecnie akceptowana odpowiedź. Uważam, że lepiej rozwiązuje również wspomnianą powyżej obawę dotyczącą „przekazywania intencji kodu”. Ponadto Tin Man zaktualizował swoją odpowiedź na aktualną wersję rubinu. To pytanie obejrzano ponad 15 000 razy. Jeśli możesz zaoszczędzić nawet 1 sekundę czasu każdego widza, myślę, że warto.
colllin
3
@collindo Dzięki, zrobiłem to. :)
Waseem

Odpowiedzi:

566

Zawsze dobrze jest przeprowadzić analizę porównawczą różnych sugerowanych odpowiedzi. Oto, co odkryłem:

#! / usr / bin / ruby

wymagają „testu porównawczego”

ary = []
1000 razy 
  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 („sort reverse”) {n.times {ary.sort {| a, b | a [: bar] <=> b [: bar]} .reverse}}
  x.report ("sort_by -a [: bar]") {n.times {ary.sort_by {| a | -a [: bar]}}}
  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}}
koniec

                          rzeczywisty system użytkownika
sort 3.960000 0,010000 3,970000 (3.990886)
sortuj wstecz 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)

Myślę, że interesujące jest to, że @ Pablo's sort_by{...}.reverse!jest najszybszy. Przed uruchomieniem testu myślałem, że będzie on wolniejszy niż „ -a[:bar]”, ale zanegowanie wartości okazuje się dłuższe niż odwrócenie całej tablicy w jednym przebiegu. Nie jest to duża różnica, ale każde małe przyspieszenie pomaga.


Pamiętaj, że te wyniki są inne w Ruby 1.9

Oto wyniki dla Ruby 1.9.3p194 (wersja 2012-04-20, wersja 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)

Są na starym MacBooku Pro. Nowsze lub szybsze maszyny będą miały niższe wartości, ale różnice względne pozostaną.


Oto nieco zaktualizowana wersja na nowszy sprzęt i wersja 2.1.1 Ruby:

#!/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)

Nowe wyniki z uruchomieniem powyższego kodu przy użyciu Ruby 2.2.1 na nowszym Macbooku Pro. Znów dokładne liczby nie są ważne, to ich relacje:

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)

Zaktualizowano dla Ruby 2.7.1 na MacBooku Pro z połowy 2015 roku:

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)

... metoda odwrotna w rzeczywistości nie zwraca odwróconej tablicy - zwraca moduł wyliczający, który rozpoczyna się na końcu i działa wstecz.

Źródłem Array#reversejest:

               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); kopiuje wskaźniki do elementów w odwrotnej kolejności, jeśli dobrze pamiętam moje C, więc tablica jest odwrócona.

Blaszany Człowiek
źródło
45
Super przydatne. Dzięki za dodatkowy wysiłek.
Joshua Pinter
7
uwielbiam, kiedy ludzie dostarczają taki test porównawczy !! Niesamowite!
ktec
25
„Uwielbiam to, gdy ludzie dostarczają taki test porównawczy !!” Ja też, bo wtedy nie muszę.
Tin Man
9
@ theTinMan Czy możesz podać TL; DR dla swojej odpowiedzi. Wszystkie te informacje o testach porównawczych są dość przydatne. Ale TL; DR na górze odpowiedzi będzie przydatne dla osób, które chcą tylko odpowiedzi. Wiem, że powinni przeczytać całe wyjaśnienie i myślę, że tak. Nadal TL; DR będzie całkiem przydatny IMHO. Dzięki za twój wysiłek.
Waseem
8
Zgadzam się z @Waseem. Jakkolwiek ta odpowiedź jest tak dobrze zbadana, PO nie zapytał „jaki jest najszybszy sposób na zejście w języku Ruby”. TL; DR u góry pokazujące proste zastosowania, a następnie testy porównawcze, poprawiłyby tę odpowiedź IMO.
89

Szybka rzecz, która oznacza zamiar malejącego porządku.

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

(Pomyśli lepszy sposób w międzyczasie);)


a.sort_by { |h| h[:bar] }.reverse!
Pablo Fernandez
źródło
Pablo, dobra robota na znalezienie lepszego sposobu! Zobacz test, który zrobiłem.
Tin Man
pierwsza metoda jest szybsza (choć może brzydsza), ponieważ zapętla się tylko raz. Po drugie, nie potrzebujesz !, to jest do operacji w miejscu.
tokland
3
Jeśli nie użyjesz huku po odwróceniu, nie zmienisz tablicy, ale utworzysz kolejną, która jest odwrócona.
Pablo Fernandez,
56

Mógłbyś:

a.sort{|a,b| b[:bar] <=> a[:bar]}
JRL
źródło
4
ale sort_bychodziło o to, żeby uniknąć wielokrotnego uruchamiania funkcji porównywania
102008
3
-1. sort_byjest o wiele bardziej wydajny i czytelniejszy. Negowanie wartości lub wykonanie odwrotności na końcu będzie szybsze i bardziej czytelne.
Marc-André Lafortune,
1
Podoba mi się ta odpowiedź, ponieważ * -1nie działa ze wszystkimi wartościami (np. Czas) i reverseponownie uporządkuje wartości, które zostały posortowane jako równe
Abe Voelker
8

Widzę, że mamy (oprócz innych) w zasadzie dwie opcje:

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

i

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

Chociaż oba sposoby dają taki sam wynik, gdy klucz sortowania jest unikalny, pamiętaj, że reversesposób odwróci kolejność kluczy, które są równe .

Przykład:

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

Chociaż często nie musisz się tym przejmować, czasem tak jest. Aby uniknąć takiego zachowania, możesz wprowadzić drugi klucz sortujący (który z pewnością musi być unikalny przynajmniej dla wszystkich elementów, które mają ten sam klucz sortujący):

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}]
Niklas Hoesl
źródło
+1 za wskazanie, że semantyka reversejest inna. Wierzę, że to też zepsuje poprzedni rodzaj, w przypadku gdy ktoś próbuje zastosować wiele rodzajów w określonej kolejności.
johncip
6

Co powiesz na:

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

To działa!!

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
źródło
Tak, to faktycznie działa, ale myślę, że PO chce wykazać zamiar za pomocą kodu (ma już działające rozwiązanie).
Pablo Fernandez
Chociaż sortbędzie działać, jest tylko szybszy podczas sortowania wartości bezpośrednich. Jeśli musisz poszukać dla nich, sort_byjest to szybsze. Zobacz test.
Tin Man,
3

W odniesieniu do wspomnianego pakietu testów porównawczych wyniki te dotyczą również posortowanych tablic.

sort_by/ reverseto jest:

# 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

A wyniki:

$: 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)
Christopher Kuttruff
źródło
Powinieneś być w stanie zrobić sort_by {}. Odwróć! (rewers bez huku tworzy nową tablicę i oczywiście spodziewam się, że będzie wolniejszy)
bibstha 15.09.17
2

Prostym rozwiązaniem od wstępującego do malejącego i odwrotnie jest:

SMYCZKI

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

CYFRY

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
źródło
1

Dla tych, którzy lubią mierzyć prędkość w 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

I wyniki:

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
Rzeczpospolita
źródło