如何在Ruby中按降序对数组进行排序
Posted
技术标签:
【中文标题】如何在Ruby中按降序对数组进行排序【英文标题】:How to sort an array in descending order in Ruby 【发布时间】:2011-02-08 04:32:16 【问题描述】:我有一个哈希数组:
[
: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]
但那个负号似乎不合适。
【问题讨论】:
考虑到其他选项,我仍然认为 -h[:bar] 是最优雅的。你不喜欢它的什么地方? 我对传达代码的意图更感兴趣。 @Waseem 当前答案没有问题。恰好有一个更好的答案。铁皮人的回答要彻底得多,并表明sort_by.reverse
比目前接受的答案要高效得多。我相信它也更好地解决了您上面提到的“传达代码意图”的问题。最重要的是,铁皮人已经更新了他对当前版本 ruby 的答案。这个问题已被查看超过 15k 次。如果你能节省每位观众 1 秒的时间,我认为这是值得的。
@collindo 谢谢我做到了。 :)
这是 2016 年,我们有另一种方式(自 v2.2 起):arr.max_by(arr.size) |h| h[:bar] #=> [:foo=>"foo", :bar=>5, :foo=>"foo", :bar=>3, :foo=>"foo", :bar=>2]
。
【参考方案1】:
对各种建议的答案进行基准测试总是很有启发性。这是我发现的:
#!/usr/bin/ruby 需要“基准” ary = [] 1000.次 ary rand(1000) n = 500 Benchmark.bm(20) 做 |x| x.report("sort") n.times ary.sort |a,b| b[:bar] a[:bar] x.report("反向排序") 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 的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)
在更新的 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)
... reverse 方法实际上并不返回一个反转数组 - 它返回一个仅从末尾开始并向后工作的枚举数。
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,则以相反的顺序复制指向元素的指针,因此数组是相反的。
【讨论】:
超级有用。感谢您的额外努力。 我喜欢人们提供这样的基准证明!太棒了! “我喜欢人们提供这样的基准证明!!”我也这样做,因为那样我就不必了。 @theTinMan 您能否提供一个 TL;DR 来回答您的问题。所有这些基准信息都非常有用。但是,答案之上的 TL;DR 对于只想要答案的人来说会很有用。我知道他们应该阅读整个解释,我认为他们会的。仍然是 TL;DR 将非常有用恕我直言。感谢您的努力。 我同意@Waseem。尽管这个答案经过充分研究,OP 并没有问“在 Ruby 中进行降序排序的最快方法是什么”。顶部的 TL;DR 显示简单用途,然后是基准,将改进 IMO 的这个答案。【参考方案2】:只是一个快速的事情,表示降序的意图。
descending = -1
a.sort_by |h| h[:bar] * descending
(同时会想到更好的方法);)
a.sort_by |h| h[:bar] .reverse!
【讨论】:
Pablo,找到更好的方法做得很好!查看我所做的基准测试。 第一种方法更快(虽然可能更丑),因为它只循环一次。至于第二个,您不需要!,这是用于就地操作的。 如果您在 reverse 后不使用 bang,您将不会反转数组,而是创建另一个反转的数组。【参考方案3】:你可以这样做:
a.sort|a,b| b[:bar] <=> a[:bar]
【讨论】:
但是使用sort_by
的重点是它避免了多次运行比较功能
-1。 sort_by
效率更高,可读性更强。最后取反或取反会更快,更易读。
我喜欢这个答案,因为* -1
不适用于所有值(例如时间),并且reverse
将重新排序排序为相等的值【参考方案4】:
我看到我们(除其他外)基本上有两种选择:
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
的语义不同。我相信在尝试以某种顺序应用多种排序的情况下,它也会弄乱先前的排序。【参考方案5】:
怎么样:
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 想通过代码显示意图(他已经有了一个可行的解决方案)。 虽然sort
可以工作,但只有在对立即数进行排序时才会更快。如果你必须为他们挖掘sort_by
更快。查看基准。【参考方案6】:
关于提到的基准套件,这些结果也适用于排序数组。
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! (没有爆炸的反向创建一个新数组,我希望它当然会更慢)【参考方案7】:对于那些喜欢在 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
【讨论】:
【参考方案8】:从升序到降序的简单解决方案是:
字符串
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]
【讨论】:
以上是关于如何在Ruby中按降序对数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章
如何使用extjs4.1在网格中按降序对带有连字符的浮点值进行排序