Ruby'de bir diziyi azalan düzende sıralama


282

Karma bir dizi var:

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

Ben :barher karma değerine göre azalan sırada bu dizi sıralamak çalışıyorum .

sort_byDizinin üzerinde sıralamak için kullanıyorum :

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

Ancak, bu dizi artan düzende sıralar. Nasıl azalan düzende sıralayabilirim?

Bir çözüm aşağıdakileri yapmaktı:

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

Ancak bu olumsuz işaret uygun görünmüyor.


4
Diğer seçenekler göz önüne alındığında hala -h [: bar] en zarif olduğunu düşünüyorum. Bu konuda neyi sevmiyorsun?
Michael Kohl

2
Kodun niyetini iletmekle daha çok ilgileniyordum.
Waseem

1
@Waseem kabul edilen cevabı güncellemenizde sorun çıkarabilir miyim?
colllin

7
@Waseem Şu anki cevapta yanlış bir şey yok. Çok daha iyi bir cevap var. Tin Man'ın cevabı çok daha kapsamlı ve sort_by.reverseşu anda kabul edilen cevaptan çok daha etkili olduğunu gösteriyor . Ayrıca, "kodun niyetini iletmek" için yukarıda bahsettiğiniz endişeyi daha iyi ele aldığını düşünüyorum. Bunun üzerine, Teneke Adam mevcut yakut versiyonu için cevabını güncelledi. Bu soru 15 binden fazla kez incelendi. Her izleyicinin zamanının 1 saniyesini bile kaydedebiliyorsanız, buna değer olduğunu düşünüyorum.
colllin

3
@collindo Teşekkürler yaptım. :)
Waseem

Yanıtlar:


566

Önerilen çeşitli cevaplar üzerinde bir kıyaslama yapmak her zaman aydınlatıcıdır. İşte buldum:

#! / Usr / bin / yakut

'kıyaslama' gerektir

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

n = 500
Benchmark.bm (20) yapmak | x |
  x.report ("sıralama") {n.times {ary.sort {| a, b | b [: bar] <=> a [: bar]}}}
  x.report ("ters sıralama") {n.times {ary.sort {| a, b | a [: bar] <=> b [: bar]} .reverse}}
  x.report ("sort_by -a [: bar]") {n.times {ary.sort_by {| a | -bir bar] } } }
  x.report ("sıralama_bir [: 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}}
son

                          kullanıcı sistemi toplam gerçek
sıralama 3.960000 0.010000 3.970000 (3.990886)
tersine sırala 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)

Sanırım @ Pablo'nun en sort_by{...}.reverse!hızlı olması ilginç . Testi çalıştırmadan önce " -a[:bar]" den daha yavaş olacağını düşündüm ama değeri reddetmek tüm diziyi bir geçişte tersine çevirmekten daha uzun sürüyor. Çok fazla bir fark yok, ama her hızlanma yardımcı oluyor.


Lütfen bu sonuçların Ruby 1.9'da farklı olduğunu unutmayın.

İşte Ruby 1.9.3p194 (2012-04-20 revizyon 35410) [x86_64-darwin10.8.0] sonuçları:

                           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)

Bunlar eski bir MacBook Pro'da. Daha yeni veya daha hızlı makineler daha düşük değerlere sahip olur, ancak göreli farklılıklar kalır.


İşte daha yeni bir donanımda biraz güncellenmiş sürüm ve Ruby'nin 2.1.1 sürümü:

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

Daha yeni bir Macbook Pro'da Ruby 2.2.1 kullanarak yukarıdaki kodu çalıştıran yeni sonuçlar. Yine, tam sayılar önemli değil, ilişkileri.

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)

2015 Ortası MacBook Pro'da Ruby 2.7.1 için güncellendi:

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)

... tersine yöntem tersine çevrilmiş bir diziyi döndürmez - en sonunda başlayan ve geriye doğru çalışan bir numaralandırıcı döndürür.

Kaynağı 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); benim C doğru hatırlıyorsanız, işaretçiler öğelere ters sırayla kopyalanıyor, böylece dizi ters.


45
Süper kullanışlı. Ekstra çaba için teşekkürler.
Joshua Pinter

7
insanlar böyle kriter kanıt sağlamak seviyorum! Müthiş!
ktec

25
"İnsanlar böyle bir kanıt kanıtlamak seviyorum!" Ben de yaparım, çünkü o zaman mecbur değilim.
Tin Man

9
@theTinMan Cevabınız için lütfen bir TL; DR verebilir misiniz? Tüm bu kıyaslama bilgileri oldukça kullanışlıdır. Ama cevabın üstündeki bir TL; sadece cevabı isteyen insanlar için faydalı olacaktır. Bütün açıklamayı okuması gerektiğini biliyorum ve bence yapacaklar. Hala bir TL; DR oldukça faydalı olacak IMHO. Çaban için teşekkürler.
Waseem

8
@Waseem ile hemfikirim. Bu cevap kadar iyi araştırılmış olan OP, "Ruby'de azalan bir sıralama yapmanın en hızlı yolu nedir" diye sormadı. En üstte basit kullanımları gösteren bir TL; DR ve ardından kriterler bu IMO cevabını iyileştirecektir.

89

Sadece hızlı bir şey, bu azalan düzenin niyetini gösterir.

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

(Bu arada daha iyi bir yol düşünecek);)


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

Pablo, daha iyi bir yol bulma konusunda iyi iş çıkardın! Yaptığım karşılaştırmaya bakın.
Tin Man

ilk yöntem daha hızlıdır (belki daha çirkin olsa da) çünkü sadece bir kez döngü yapar. İkincisi ise,!
tokland

3
Eğer tersi ardına patlama kullanmazsanız, diziyi tersine çevirmezsiniz, tersine çevrilmiş başka bir diziyi yaratırsınız.
Pablo Fernandez

56

Şunları yapabilirsiniz:

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

4
ancak tüm kullanım noktası sort_bykarşılaştırma işlevini pek çok kez çalıştırmaktan kaçınmasıydı
user102008

3
-1. sort_byçok daha verimli ve daha okunabilir. Değeri reddetmek veya sonunda geri yapmak daha hızlı ve daha okunabilir olacaktır.
Marc-André Lafortune

1
Bu cevabı beğendim çünkü * -1tüm değerlerle (örneğin Zaman) reverseçalışmaz ve eşit olarak sıralanan değerleri yeniden sıralar
Abe Voelker

8

Görüyorum ki (diğerlerinin yanında) temel olarak iki seçeneğimiz var:

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

ve

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

Sıralama anahtarınız benzersiz olduğunda her iki yol da size aynı sonucu verirken, reverseyolun eşit olan anahtarların sırasını tersine çevireceğini unutmayın .

Misal:

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

Sık sık bunu önemsemenize gerek yokken, bazen yaparsınız. Bu tür davranışlardan kaçınmak için ikinci bir sıralama anahtarı getirebilirsiniz (en azından aynı sıralama anahtarına sahip tüm öğeler için benzersiz olması gerekir):

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

Anlambiliminin reversefarklı olduğunu göstermek için +1 . Ben de bir tür bir sırayla birden çok tür uygulamaya çalıştığı durumda, önceki bir tür berbat olacağına inanıyorum.
johncip

6

Ne dersin:

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

İşe yarıyor!!

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

Evet gerçekten işe yarıyor, ama PO'nun kodla niyet göstermek istediğini düşünüyorum (zaten çalışan bir çözümü var).
Pablo Fernandez

Çalışacak olsa sortda, sadece anlık değerleri sıralarken daha hızlıdır. Eğer onlar için kazmak zorunda sort_bydaha hızlı. Kıyaslama bölümüne bakınız.
Tin Man

3

Bahsedilen kıyaslama paketi ile ilgili olarak, bu sonuçlar sıralı diziler için de geçerlidir.

sort_by/ reversebu:

# 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

Ve sonuçlar:

$: 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} tarafından {} ters çevirebilmeniz gerekir! (patlama olmadan ters yeni bir dizi oluşturur ve tabii ki daha yavaş olmasını beklerdim)
bibstha

2

Yükselişten inişe ya da tam tersi basit çözüm:

TELLER

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

DIGITS

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]

1

IPS'de hızı ölçmeyi sevenler için;)

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

Ve sonuçlar:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.