루비에서 배열을 내림차순으로 정렬하는 방법


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
@Waseem 허용 된 답변을 업데이트하는 데 문제가 있습니까?
colllin

7
@Waseem 현재 답변에 아무런 문제가 없습니다. 훨씬 더 나은 대답이 있습니다. Tin Man의 답변은 훨씬 더 철저하며 sort_by.reverse현재 인정되는 답변보다 훨씬 더 효율적입니다. 또한 "코드 의도 전달"에 대해 위에서 언급 한 문제를 해결하는 것이 좋습니다. 또한 Tin Man은 현재 버전의 루비에 대한 답변을 업데이트했습니다. 이 질문은 15k 번 이상 조회되었습니다. 각 시청자의 시간을 1 초만 절약 할 수 있다면 그만한 가치가 있다고 생각합니다.
colllin

3
@collindo 감사합니다. :)
Waseem

답변:


566

다양한 제안 된 답변에 대한 벤치 마크를 수행하는 것이 항상 깨달았습니다. 내가 찾은 것은 다음과 같습니다.

#! / usr / bin / ruby

'벤치 마크'가 필요합니다

ary = []
1000. 회 { 
  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 | -술집] } } }
  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)
역순 정렬 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 's 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에 있습니다. 새롭거나 더 빠른 컴퓨터는 더 낮은 값을 갖지만 상대적인 차이는 유지됩니다.


다음은 최신 하드웨어에서 약간 업데이트 된 버전과 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)

최신 Macbook Pro에서 Ruby 2.2.1을 사용하여 위의 코드를 실행하는 새로운 결과. 다시 말하지만 정확한 숫자는 중요하지 않으며 관계입니다.

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 년 중순 MacBook Pro에서 Ruby 2.7.1 용으로 업데이트 :

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
"사람들이 이와 같은 벤치 마크 증거를 제공 할 때 나는 그것을 좋아한다 !!" 그럴 필요도 없기 때문에 나도 그렇게한다.
Tin Man

9
@theTinMan 귀하의 답변에 TL; DR을 제공 할 수 있습니까? 이 모든 벤치 마크 정보는 매우 유용합니다. 그러나 답변 위에 TL; DR은 답변을 원하는 사람들에게 유용 할 것입니다. 나는 그들이 전체 설명을 읽어야한다는 것을 알고 있습니다. 여전히 TL; DR은 매우 유용합니다 IMHO. 노력해 주셔서 감사합니다.
Waseem

8
@Waseem에 동의합니다. 이 답변이 잘 연구 된 것처럼 OP는 "루비에서 내림차순 정렬을 수행하는 가장 빠른 방법은 무엇입니까?"를 묻지 않았습니다. 간단한 사용과 벤치 마크를 보여주는 맨 위에있는 TL; DR은이 답변 IMO를 개선 할 것입니다.

89

내림차순의 의도를 나타내는 빠른 것입니다.

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

(그동안 더 나은 방법을 생각할 것입니다);)


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

파블로, 더 나은 길을 찾는 좋은 일! 내가 한 벤치 마크를 참조하십시오.
Tin Man

첫 번째 방법은 한 번만 반복되기 때문에 더 빠릅니다. 두 번째로, 당신은!가 필요하지 않습니다.
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
이 답변 * -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}]

의 의미 reverse가 다르다는 것을 지적한 +1 . 여러 정렬을 어떤 순서로 적용하려고하는 경우 이전 정렬을 망칠 것이라고 생각합니다.
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것이 더 빠릅니다. 벤치 마크를 참조하십시오.
Tin Man

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 {}. reverse를 수행 할 수 있어야합니다! (강타는 새로운 배열을 만들고 난 그 코스의 느린 것으로 기대하지 않고 역)
bibstha

2

오름차순에서 내림차순으로 또는 그 반대로 간단한 솔루션은 다음과 같습니다.

STRINGS

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

치수

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.