วิธีการจัดเรียงอาร์เรย์ตามลำดับจากมากไปน้อยใน 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] }

แต่เครื่องหมายลบนั้นดูไม่เหมาะสม


4
เมื่อพิจารณาตัวเลือกอื่นฉันยังคงคิดว่า -h [: bar] เป็นสิ่งที่หรูหราที่สุด คุณไม่ชอบอะไรเกี่ยวกับเรื่องนี้?
Michael Kohl

2
ฉันสนใจที่จะถ่ายทอดความตั้งใจของรหัสมากขึ้น
Waseem

1
@aseemem ฉันสามารถรบกวนคุณในการปรับปรุงคำตอบที่ยอมรับได้หรือไม่?
colllin

7
@ Waseem ไม่มีอะไรผิดปกติกับคำตอบปัจจุบัน มีเพียงคำตอบที่ดีกว่า คำตอบของ Tin Man นั้นละเอียดและแสดงให้เห็นว่าsort_by.reverseมีประสิทธิภาพมากกว่าคำตอบที่ยอมรับในปัจจุบัน ฉันเชื่อว่ามันยังดีกว่าที่อยู่ข้อกังวลที่คุณกล่าวถึงข้างต้นสำหรับ "ถ่ายทอดเจตนาของรหัส" ยิ่งไปกว่านั้น Tin Man ได้อัปเดตคำตอบสำหรับทับทิมรุ่นปัจจุบันแล้ว คำถามนี้มีการดูมากกว่า 15k ครั้ง หากคุณสามารถประหยัดเวลาได้ 1 วินาทีในการดูแต่ละครั้งฉันคิดว่ามันคุ้มค่า
colllin

3
@collindo ขอบคุณฉันทำมัน :)
Waseem

คำตอบ:


566

มันเป็นความสว่างเสมอที่จะทำมาตรฐานในคำตอบที่แนะนำต่าง ๆ นี่คือสิ่งที่ฉันค้นพบ:

#! / usr / bin / ทับทิม

ต้องการ 'มาตรฐาน'

ary = []
1,000.times { 
  ary << {: bar => rand (1,000)} 
}

n = 500
Benchmark.bm (20) ทำ | 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}}
ปลาย

                          ระบบผู้ใช้จริงทั้งหมด
จัดเรียง 3.960000 0.010000 3.970000 (3.990886)
sort reverse 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 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 กลางปี ​​2558:

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
สุดยอดประโยชน์ ขอบคุณสำหรับความพยายามพิเศษ
Joshua Pinter

7
ฉันชอบเมื่อมีคนแสดงหลักฐานมาตรฐานเช่นนี้ !! ! น่ากลัว
ktec

25
"ฉันรักเมื่อมีคนให้หลักฐานการเปรียบเทียบเช่นนี้ !!" ฉันก็ทำเช่นนั้นเพราะฉันไม่ต้องทำ
มนุษย์ดีบุก

9
@theTinMan คุณช่วยระบุ TL; DR สำหรับคำตอบของคุณได้ไหม ข้อมูลมาตรฐานทั้งหมดนี้มีประโยชน์มาก แต่ TL; DR ที่อยู่เหนือคำตอบจะเป็นประโยชน์สำหรับผู้ที่ต้องการคำตอบ ฉันรู้ว่าพวกเขาควรอ่านคำอธิบายทั้งหมดและฉันคิดว่าพวกเขาจะ ยังเป็น TL; DR จะมีประโยชน์มากกับ IMHO ขอบคุณสำหรับความพยายามของคุณ
Waseem

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
หากคุณไม่ได้ใช้เสียงระเบิดหลังจากย้อนกลับคุณจะไม่ย้อนกลับอาร์เรย์ แต่สร้างอีกอันที่ตรงกันข้าม
Pablo Fernandez

56

คุณสามารถทำได้:

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

4
แต่ประเด็นทั้งหมดของการใช้sort_byคือมันหลีกเลี่ยงการเรียกใช้ฟังก์ชันการเปรียบเทียบหลายครั้ง
user102008

3
-1 sort_byมีประสิทธิภาพมากขึ้นและอ่านได้มากขึ้น การลบค่าหรือทำสิ่งที่ตรงกันข้ามในตอนท้ายจะเร็วขึ้นและอ่านง่ายขึ้น
Marc-André Lafortune

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แตกต่าง ฉันเชื่อว่ามันจะเลอะเรียงลำดับก่อนหน้าในกรณีที่พยายามใช้หลายเรียงลำดับ
johncip

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

ใช่มันใช้งานได้จริง แต่ฉันคิดว่า PO ต้องการแสดงเจตนาด้วยรหัส (เขามีวิธีแก้ปัญหาที่ใช้งานได้แล้ว)
Pablo Fernandez

ในขณะที่ใช้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"]

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;)

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.