给定数组[X,Y]的数组:
a=[[1,2],[2,2],[3,2],[4,2],[5,2],[6,2]]
对2<=X<4
的所有Y位数求和的最有效方法是什么?
答案 0 :(得分:4)
我正在使用它:
a.select{ |x,y| (2...4) === x }.inject(0){ |m, (x,y)| m + y }
=> 4
我并不喜欢使用...
,因为它会让人们误解它的工作方式。以下是一些等效的测试方法:
a.select{ |x,y| (2..3) === x }.inject(0){ |m, (x,y)| m + y }
ary.select{ |x,y| (2 <= x) && (x < 4) }.inject(0){ |m, (x,y)| m + y } } }
这里有一些基准代码:
require 'benchmark'
a = [ [1,2], [2,2], [3,2], [4,2], [5,2], [6,2] ]
n = 1_000_000
Benchmark.bm(12) do |b|
b.report('The Tin Man') { n.times { a.select{ |x,y| (2...4) === x }.inject(0){ |m, (x,y)| m + y } } }
b.report('The Tin Man2') { n.times { a.select{ |x,y| (2 <= x) && (x < 4) }.inject(0){ |m, (x,y)| m + y } } }
b.report('Mik_Die') { n.times { a.select{ |i| (2...4).include? i[0] }.map(&:last).reduce(:+) } }
b.report('Justin Ko') { n.times { a.inject(0){ |sum, coord| (coord[0] >= 2 and coord[0] < 4) ? sum + coord[1] : sum } } }
b.report('Justin Ko2') { n.times { a.inject(0){ |sum, (x,y)| (x >= 2 and x < 4) ? sum + y : sum } } }
b.report('Leo Correa') { n.times { sum = 0; a.each { |x, y| sum += y if x >= 2 and x < 4 } } }
b.report('tokland') { n.times { a.map { |x, y| y if x >= 2 && x < 4 }.compact.inject(0, :+) } }
end
及其输出:
user system total real The Tin Man 4.020000 0.000000 4.020000 ( 4.020154) The Tin Man2 2.420000 0.000000 2.420000 ( 2.424424) Mik_Die 3.830000 0.000000 3.830000 ( 3.836531) Justin Ko 2.070000 0.000000 2.070000 ( 2.072446) Justin Ko2 2.000000 0.000000 2.000000 ( 2.035079) Leo Correa 1.260000 0.000000 1.260000 ( 1.259672) tokland 2.650000 0.010000 2.660000 ( 2.645466)
这里学到的教训是inject
代价高昂。
答案 1 :(得分:3)
我会使用inject
:
a = [[1,2],[2,2],[3,2],[4,2],[5,2],[6,2]]
sum = a.inject(0){ |sum, (x,y)| (x >= 2 and x < 4) ? sum + y : sum }
puts sum
#=> 4
rdoc很好地描述了inject
方法:
注入(初始){|备忘录,obj |阻止}→obj
通过应用二进制操作组合枚举的所有元素, 由命名方法或运算符的块或符号指定。
如果指定一个块,则对于枚举中的每个元素,该块为 传递了累加器值(memo)和元素。如果你指定一个 而是符号,然后将集合中的每个元素传递给 备忘录的命名方法。在任何一种情况下,结果都成为新的 备忘录的价值。在迭代结束时,备忘录的最终值 是方法的返回值。
如果没有明确指定备忘录的初始值,则使用 第一个集合元素用作备忘录的初始值。
更新 - 基准数组与拆包:
@tokland建议拆开这些配对,这肯定会提高可读性。运行以下基准测试以查看它是否比使用阵列更快(即我的原始解决方案)。
require 'benchmark'
a = [ [1,2], [2,2], [3,2], [4,2], [5,2], [6,2] ]
n = 2_000_000
Benchmark.bm(12) do |b|
b.report('array'){n.times{a.inject(0){ |sum, coord| (coord[0] >= 2 and coord[0] < 4) ? sum + coord[1] : sum }}}
b.report('unpacked'){n.times{a.inject(0){ |sum, (x,y)| (x >= 2 and x < 4) ? sum + y : sum }}}
end
给出了结果
user system total real
array 3.916000 0.000000 3.916000 ( 3.925393)
unpacked 3.619000 0.000000 3.619000 ( 3.616361)
所以,至少在这种情况下,拆包对更好。
答案 2 :(得分:1)
我喜欢@JustinKo给出的注入答案,但是如果你是Ruby的新手,这里的另一个解决方案可能更容易理解。
a=[[1,2],[2,2],[3,2],[4,2],[5,2],[6,2]]
sum = 0
a.each { |x, y| sum += y if x >= 2 and x < 4 }
puts sum
#=> 4
答案 3 :(得分:0)
使用更简单方法的链条在ruby中更明显。所以:
a=[[1,2],[2,2],[3,2],[4,2],[5,2],[6,2]]
a.select{ |i| (2...4).include? i[0] }.map(&:last).reduce(:+)
# => 4
答案 4 :(得分:0)
从概念上讲,您想要使用的是列表理解。唉,Ruby没有LC的内置语法,但是一个紧凑的+ map可以很好地完成工作:
a.map { |x, y| y if x >= 2 && x < 4 }.compact.inject(0, :+)
#=> 4
如果你正在编写一个中/大脚本,你可能已经(并且应该有)扩展 模块。添加所需的方法,以便编写声明性和简洁的代码:
a.map_select { |x, y| y if x >= 2 && x < 4 }.sum
甚至:
a.sum { |x, y| y if x >= 2 && x < 4 }