1

给定数组 [X,Y] 的数组:

a=[[1,2],[2,2],[3,2],[4,2],[5,2],[6,2]]

将所有 Y 数字求和的最有效方法是2<=X<4什么?

4

5 回答 5

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

及其输出:

                   用户系统总真实
铁皮人 4.020000 0.000000 4.020000 ( 4.020154)
铁皮人2 2.420000 0.000000 2.420000 ( 2.424424)
Mik_Die 3.830000 0.000000 3.830000 (3.836531)
贾斯汀高 2.070000 0.000000 2.070000 (2.072446)
贾斯汀 Ko2 2.000000 0.000000 2.000000 (2.035079)
利奥·科雷亚 1.260000 0.000000 1.260000 (1.259672)
托克兰 2.650000 0.010000 2.660000 ( 2.645466)

这里吸取的教训inject是代价高昂的。

于 2013-01-21T19:14:47.373 回答
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 | 块 } → 对象

通过应用二元运算组合枚举的所有元素,该二元运算由命名方法或运算符的块或符号指定。

如果您指定一个块,那么对于枚举中的每个元素,该块都会传递一个累加器值(备忘录)和元素。如果你指定了一个符号,那么集合中的每个元素都将被传递给 memo 的命名方法。无论哪种情况,结果都会成为 memo 的新值。在迭代结束时,memo 的最终值就是方法的返回值。

如果没有显式指定 memo 的初始值,则使用 collection 的第一个元素作为 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)

所以,至少在这种情况下,解包对更好。

于 2013-01-21T18:49:49.773 回答
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
于 2013-01-21T18:59:45.440 回答
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 
于 2013-01-21T19:15:54.370 回答
0

从概念上讲,您想要使用的是列表理解。唉,Ruby 没有内置的 LC 语法,但是 compact+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 }
于 2013-01-21T19:42:50.177 回答