Cara mengurutkan array dalam urutan menurun di Ruby

282

Saya memiliki berbagai hash:

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

Saya mencoba untuk mengurutkan array ini dalam urutan menurun sesuai dengan nilai :bardalam setiap hash.

Saya menggunakan sort_byuntuk mengurutkan array di atas:

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

Namun, ini mengurutkan array dalam urutan menaik. Bagaimana cara membuatnya dalam urutan menurun?

Salah satu solusinya adalah melakukan hal berikut:

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

Tetapi tanda negatif itu sepertinya tidak tepat.

Waseem
sumber
4
Diberikan opsi lain, saya masih berpikir -h [: bar] adalah yang paling elegan. Apa yang tidak kamu sukai dari itu?
Michael Kohl
2
Saya jauh lebih tertarik untuk menyampaikan maksud kode.
Waseem
1
@Waseem, bisakah saya membuat Anda kesulitan untuk memperbarui jawaban yang diterima?
colllin
7
@Waseem Tidak ada yang salah dengan jawaban saat ini. Kebetulan ada jawaban yang jauh lebih baik. jawaban Tin Man jauh lebih teliti dan menunjukkan bahwa sort_by.reversesecara dramatis lebih efisien daripada jawaban yang saat ini diterima. Saya percaya ini juga lebih baik mengatasi masalah yang Anda sebutkan di atas untuk "menyampaikan maksud kode". Selain itu, Manusia Timah telah memperbarui jawabannya untuk versi ruby ​​saat ini. Pertanyaan ini telah dilihat lebih dari 15 ribu kali. Jika Anda dapat menghemat bahkan 1 detik dari setiap waktu pemirsa, saya pikir itu sepadan.
colllin
3
@collindo Terima kasih sudah melakukannya. :)
Waseem

Jawaban:

566

Itu selalu mencerahkan untuk melakukan tolok ukur pada berbagai jawaban yang disarankan. Inilah yang saya temukan:

#! / usr / bin / ruby

membutuhkan 'tolok ukur'

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

n = 500
Benchmark.bm (20) lakukan | 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 | -sebuah 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}}
akhir

                          total sistem pengguna nyata
sortir 3.960000 0.010000 3.970000 (3.990886)
sortir mundur 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)

Saya pikir ini menarik bahwa @ Pablo's sort_by{...}.reverse!tercepat. Sebelum menjalankan tes saya pikir ini akan lebih lambat daripada " -a[:bar]" tetapi meniadakan nilainya ternyata membutuhkan waktu lebih lama daripada membalik seluruh array dalam satu lintasan. Tidak banyak perbedaan, tetapi setiap percepatan kecil membantu.


Harap dicatat bahwa hasil ini berbeda di Ruby 1.9

Berikut adalah hasil untuk Ruby 1.9.3p194 (2012-04-20 revisi 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)

Ini ada di MacBook Pro lama. Mesin yang lebih baru, atau yang lebih cepat, akan memiliki nilai yang lebih rendah, tetapi perbedaan relatif akan tetap ada.


Berikut ini adalah versi yang sedikit diperbarui pada perangkat keras yang lebih baru dan versi 2.1.1 dari 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)

Hasil baru menjalankan kode di atas menggunakan Ruby 2.2.1 pada Macbook Pro yang lebih baru. Sekali lagi, angka pastinya tidak penting, ini adalah hubungan mereka:

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)

Diperbarui untuk Ruby 2.7.1 pada Mid-2015 MacBook Pro:

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)

... metode terbalik sebenarnya tidak mengembalikan array yang terbalik - metode ini mengembalikan enumerator yang baru saja dimulai di akhir dan bekerja mundur.

Sumbernya Array#reverseadalah:

               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); menyalin pointer ke elemen dalam urutan terbalik jika saya ingat C saya dengan benar, sehingga array dibalik.

Manusia Timah
sumber
45
Sangat berguna. Terima kasih atas usaha ekstra.
Joshua Pinter
7
saya suka kalau orang memberikan bukti tolok ukur seperti ini !! Luar biasa!
ktec
25
"Aku suka kalau orang memberikan bukti tolok ukur seperti ini !!" Saya juga melakukannya, karena saya tidak perlu melakukannya.
Manusia Timah
9
@ theTinMan Bisakah Anda memberikan TL; DR untuk jawaban Anda. Semua informasi benchmark ini cukup bermanfaat. Tetapi TL; DR di atas jawaban akan berguna bagi orang yang hanya menginginkan jawabannya. Saya tahu mereka harus membaca seluruh penjelasan dan saya pikir mereka akan melakukannya. Masih TL; DR akan menjadi IMHO cukup berguna. Terima kasih atas usaha anda
Waseem
8
Saya setuju dengan @Waseem. Sebagaimana diteliti dengan baik seperti jawaban ini, OP tidak bertanya "apa cara tercepat untuk melakukan semacam penurunan di Ruby". TL; DR di bagian atas menunjukkan penggunaan sederhana, diikuti oleh tolok ukur, akan meningkatkan jawaban IMO ini.
89

Hanya hal cepat, yang menunjukkan niat menurun.

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

(Akan memikirkan cara yang lebih baik untuk sementara waktu);)


a.sort_by { |h| h[:bar] }.reverse!
Pablo Fernandez
sumber
Pablo, pekerjaan bagus untuk menemukan cara yang lebih baik! Lihat patokan yang saya lakukan.
the Tin Man
metode pertama lebih cepat (walaupun mungkin lebih buruk) karena hanya loop sekali. Adapun yang kedua, Anda tidak perlu! Itu untuk operasi inplace.
tokland
3
Jika Anda tidak menggunakan bang setelah membalikkan Anda tidak akan membalik array tetapi membuat yang lain terbalik.
Pablo Fernandez
56

Anda bisa melakukannya:

a.sort{|a,b| b[:bar] <=> a[:bar]}
JRL
sumber
4
tetapi inti dari penggunaannya sort_byadalah bahwa ia menghindari menjalankan fungsi perbandingan berkali-kali
user102008
3
-1. sort_byjauh lebih efisien, dan lebih mudah dibaca. Meniadakan nilai atau melakukan pembalikan pada akhirnya akan lebih cepat dan lebih mudah dibaca.
Marc-André Lafortune
1
Saya suka jawaban ini karena * -1tidak bekerja dengan semua nilai (misalnya Waktu), dan reverseakan memesan ulang nilai yang diurutkan sama
Abe Voelker
8

Saya melihat bahwa kita memiliki (di samping yang lain) pada dasarnya dua opsi:

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

dan

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

Meskipun kedua cara memberi Anda hasil yang sama ketika kunci penyortiran Anda unik, perlu diingat bahwa reversecara tersebut akan membalik urutan tombol yang sama .

Contoh:

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

Meskipun Anda sering tidak perlu peduli tentang ini, kadang-kadang Anda perlu peduli. Untuk menghindari perilaku seperti itu, Anda bisa memperkenalkan kunci penyortiran kedua (yang pasti harus unik setidaknya untuk semua item yang memiliki kunci penyortiran yang sama):

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
sumber
+1 untuk menunjukkan bahwa semantiknya reverseberbeda. Saya percaya ini juga akan mengacaukan jenis sebelumnya, dalam kasus di mana seseorang mencoba menerapkan beberapa jenis dalam urutan tertentu.
johncip
6

Bagaimana dengan:

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

Berhasil!!

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
sumber
Ya itu benar-benar berfungsi, tapi saya pikir PO ingin menunjukkan maksud dengan kode (dia sudah punya solusi yang berfungsi).
Pablo Fernandez
Meskipun sortakan berfungsi, itu hanya lebih cepat ketika menyortir nilai langsung. Jika Anda harus menggali untuk mereka sort_bylebih cepat. Lihat patokannya.
the Tin Man
3

Mengenai paket benchmark yang disebutkan, hasil ini juga berlaku untuk array yang diurutkan.

sort_by/ reverseitu adalah:

# 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

Dan hasilnya:

$: 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
sumber
Anda seharusnya dapat melakukan sort_by {}. Mundur! (membalikkan tanpa bang menciptakan array baru dan saya berharap itu menjadi lebih lambat tentu saja)
bibstha
2

Solusi sederhana dari naik ke turun dan sebaliknya adalah:

STRING

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

DIGIT

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
sumber
1

Untuk orang-orang yang suka mengukur kecepatan dalam 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

Dan hasil:

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
sumber