Cách sắp xếp một mảng theo thứ tự giảm dần trong Ruby


281

Tôi có một mảng băm:

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

Tôi đang cố gắng sắp xếp mảng này theo thứ tự giảm dần theo giá trị của :barmỗi hàm băm.

Tôi đang sử dụng sort_byđể sắp xếp mảng trên:

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

Tuy nhiên, điều này sắp xếp các mảng theo thứ tự tăng dần. Làm thế nào để tôi sắp xếp nó theo thứ tự giảm dần?

Một giải pháp là làm như sau:

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

Nhưng dấu hiệu tiêu cực đó có vẻ không phù hợp.


4
Đưa ra các tùy chọn khác tôi vẫn nghĩ -h [: bar] là thanh lịch nhất. Bạn không thích điều gì về nó?
Michael Kohl

2
Tôi đã quan tâm nhiều hơn đến việc truyền đạt ý định của mã.
Waseem

1
@Waseem tôi có thể làm phiền bạn để cập nhật câu trả lời được chấp nhận không?
colllin

7
@Waseem Không có gì sai với câu trả lời hiện tại. Có một câu trả lời tốt hơn nhiều. Câu trả lời của Tin Man kỹ lưỡng hơn nhiều và cho thấy sort_by.reversehiệu quả rõ rệt hơn câu trả lời hiện được chấp nhận. Tôi tin rằng nó cũng giải quyết tốt hơn mối quan tâm mà bạn đã đề cập ở trên để "truyền đạt ý định của mã". Trên hết, Tin Man đã cập nhật câu trả lời của mình cho phiên bản ruby ​​hiện tại. Câu hỏi này đã được xem hơn 15k lần. Nếu bạn có thể tiết kiệm thậm chí 1 giây thời gian của mỗi người xem, tôi nghĩ rằng nó đáng giá.
colllin

3
@collindo Cảm ơn tôi đã làm nó. :)
Waseem

Câu trả lời:


564

Luôn luôn khai sáng để làm điểm chuẩn cho các câu trả lời được đề xuất khác nhau. Đây là những gì tôi phát hiện ra:

#! / usr / bin / hồng ngọc

yêu cầu 'điểm chuẩn'

ary = []
1000 lần 
  ary << {: bar => rand (1000)} 
}

n = 500
Điểm chuẩn.bm (20) làm | x |
  x.report ("sắp xếp") {n.times {ary.sort {| a, b | b [: bar] <=> a [: bar]}}}
  x.report ("sắp xếp ngược") {n.times {ary.sort {| a, b | a [: bar] <=> b [: bar]} .reverse}}
  x.report ("sort_by -a [: bar]") {n.times {ary.sort_by {| a | -một quán bar] } } }
  x.report ("sort_by a [: bar] * - 1") {n.times {ary.sort_by {| a | một [: thanh] * - 1}}}
  x.report ("sort_by.reverse!") {n.times {ary.sort_by {| a | một [: bar]} .reverse}}
kết thúc

                          tổng hệ thống người dùng thực
sắp xếp 3,960000 0,010000 3,970000 (3,990886)
sắp xếp ngược 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,6500 0,000000 0,650000 (0,654447)

Tôi nghĩ thật thú vị khi @ Pablo sort_by{...}.reverse!là nhanh nhất. Trước khi chạy thử nghiệm, tôi nghĩ rằng nó sẽ chậm hơn " -a[:bar]" nhưng việc phủ nhận giá trị hóa ra mất nhiều thời gian hơn để đảo ngược toàn bộ mảng trong một lần. Nó không có nhiều sự khác biệt, nhưng mỗi lần tăng tốc đều giúp ích.


Xin lưu ý rằng những kết quả này khác nhau trong Ruby 1.9

Dưới đây là kết quả cho Ruby 1.9.3p194 (phiên bản 2012-04-20 sửa đổi 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)

Đây là trên một chiếc MacBook Pro cũ. Máy mới hơn hoặc nhanh hơn sẽ có giá trị thấp hơn, nhưng sự khác biệt tương đối sẽ vẫn còn.


Đây là phiên bản cập nhật một chút trên phần cứng mới hơn và phiên bản 2.1.1 của 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)

Kết quả mới chạy mã trên bằng Ruby 2.2.1 trên Macbook Pro mới hơn. Một lần nữa, những con số chính xác không quan trọng, đó là mối quan hệ của họ:

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)

Cập nhật cho Ruby 2.7.1 trên MacBook Pro giữa năm 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)

... Phương thức đảo ngược không thực sự trả về một mảng đảo ngược - nó trả về một liệt kê chỉ bắt đầu ở cuối và hoạt động ngược.

Nguồn cho Array#reverselà:

               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); đang sao chép các con trỏ vào các phần tử theo thứ tự ngược lại nếu tôi nhớ chính xác C của mình, vì vậy mảng bị đảo ngược.


45
Siêu hữu ích. Cảm ơn cho những nỗ lực thêm.
Joshua Pinter

7
tôi thích nó khi mọi người cung cấp bằng chứng chuẩn như thế này !! Tuyệt vời!
ktec

25
"Tôi thích nó khi mọi người cung cấp bằng chứng chuẩn như thế này !!" Tôi cũng vậy, vì sau đó tôi không phải làm thế.
Tin Man

9
@theTinMan Bạn có thể vui lòng cung cấp TL; DR cho câu trả lời của bạn không. Tất cả thông tin điểm chuẩn này là khá hữu ích. Nhưng một TL; DR trên đầu câu trả lời sẽ hữu ích cho những người chỉ muốn câu trả lời. Tôi biết họ nên đọc toàn bộ lời giải thích và tôi nghĩ họ sẽ làm được. Vẫn là một TL; DR sẽ khá hữu ích IMHO. Cảm ơn nỗ lực của bạn.
Waseem

8
Tôi đồng ý với @Waseem. Được nghiên cứu kỹ lưỡng như câu trả lời này, OP đã không hỏi "cách nhanh nhất để thực hiện sắp xếp giảm dần trong Ruby" là gì. Một TL; DR ở đầu hiển thị các cách sử dụng đơn giản, theo sau là điểm chuẩn, sẽ cải thiện câu trả lời này IMO.

89

Chỉ cần một điều nhanh chóng, điều đó biểu thị ý định của trật tự giảm dần.

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

(Sẽ nghĩ ra một cách tốt hơn trong thời gian trung bình);)


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

Pablo, công việc tốt đẹp để tìm một cách tốt hơn! Xem điểm chuẩn tôi đã làm.
Tin Man

phương pháp đầu tiên nhanh hơn (mặc dù có thể xấu hơn) vì nó chỉ lặp một lần. Đối với lần thứ hai, bạn không cần!, Đó là cho các hoạt động tại chỗ.
tokland

3
Nếu bạn không sử dụng tiếng nổ sau khi đảo ngược, bạn sẽ không đảo ngược mảng mà tạo một mảng khác bị đảo ngược.
Pablo Fernandez

56

Bạn có thể làm:

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

4
nhưng toàn bộ vấn đề sử dụng sort_bylà nó đã tránh chạy chức năng so sánh rất nhiều lần
user102008

3
-1. sort_bylà rất hiệu quả hơn, và dễ đọc hơn. Việc phủ định giá trị hoặc thực hiện đảo ngược ở cuối sẽ nhanh hơn và dễ đọc hơn.
Marc-André Lafortune

1
Tôi thích câu trả lời này vì * -1không hoạt động với tất cả các giá trị (ví dụ: Thời gian) và reversesẽ sắp xếp lại các giá trị được sắp xếp bằng nhau
Abe Voelker

8

Tôi thấy rằng về cơ bản chúng ta có (bên cạnh những người khác) hai tùy chọn:

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

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

Mặc dù cả hai cách đều cho bạn cùng một kết quả khi khóa sắp xếp của bạn là duy nhất, hãy nhớ rằng reversecách đó sẽ đảo ngược thứ tự các khóa bằng nhau .

Thí dụ:

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

Mặc dù bạn thường không cần quan tâm đến điều này, đôi khi bạn cũng vậy. Để tránh hành vi như vậy, bạn có thể giới thiệu khóa sắp xếp thứ hai (chắc chắn cần phải là duy nhất ít nhất cho tất cả các mục có cùng khóa sắp xếp):

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 để chỉ ra rằng ngữ nghĩa của reverselà khác nhau. Tôi tin rằng nó cũng sẽ làm rối tung một loại trước đó, trong trường hợp một người đang cố gắng áp dụng nhiều loại theo thứ tự.
johncip

6

Thế còn:

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

Nó hoạt động !!

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

Vâng nó thực sự hoạt động, nhưng tôi nghĩ PO muốn thể hiện ý định với mã (anh ấy đã có giải pháp làm việc rồi).
Pablo Fernandez

Trong khi sortsẽ hoạt động, nó chỉ nhanh hơn khi sắp xếp các giá trị ngay lập tức. Nếu bạn phải đào cho họ sort_bylà nhanh hơn. Xem điểm chuẩn.
Tin Man

3

Liên quan đến bộ điểm chuẩn được đề cập, những kết quả này cũng giữ cho các mảng được sắp xếp.

sort_by/ reverseđó là:

# 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

Và kết quả:

$: 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)

Bạn sẽ có thể thực hiện sort_by {}. Ngược lại! (đảo ngược mà không có tiếng nổ tạo ra một mảng mới và tất nhiên tôi hy vọng điều đó sẽ chậm hơn)
bibstha

2

Giải pháp đơn giản từ tăng dần đến giảm dần và ngược lại là:

DÂY

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

THUỐC

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

Dành cho những người thích đo tốc độ trong 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

Và kết quả:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.