性能:遍历一个数组,但排除一个元素

3
我正在使用 Ruby on Rails 3.0.7,并且我想遍历一个对象数组(类实例),但不包括 id 等于 1 的元素(id 指的是数组[1]索引)。
我知道我可以在 each 语句内部使用 if 语句,然后检查每个“当前”/“考虑”的元素是否满足 id == 1。但由于数组中有很多数据,我希望找到另一种更高效的方法来完成相同的事情(避免每次运行 if)。
我该怎么做?

为什么不先确保数组不以id为1开始填充呢? - Mark Thomas
1
为什么 Ruby 中没有类似于 array.except_element 的方法? - Amol Pujari
4个回答

7
  1. 使程序正常工作
  2. 个人资料
  3. 优化

唐纳德·克努斯(Donald Knuth)曾说:

我们应该忘记小的效率问题,大约97%的时间都是这样:过早的优化是万恶之源。

现在,你可以做一些像这样的事情:

def f
  do_something
end

f 0
for i in 2..n
  f i
end

甚至可以这样做:
def f
  yield 0
  for i in 2..@n
    yield i
  end
end

f do |i|
  do_something
end

但是您可能并不想执行这样的操作,如果确实需要执行,那么只有在确定其重要性之后才会执行。

最后,假设这个丑陋的技巧确实使您的服务器运行速度略微提高。这值得吗?


2
+1,正是我想说的。通常情况下,if id == 1 的性能非常好。除非你在后续发现实际的性能问题,否则不必过于担心,然后启动你的分析器。 - Jordan Running

1

if语句是一种非常廉价的操作。您可以使用标准基准测试工具进行检查。

require "benchmark"

array = [1] * 100_000

Benchmark.bm do |bm|
  bm.report "with if" do
    array.each_with_index do |element, i|
      next if i == 1
      element - 1
    end
  end

  bm.report "without if" do
    array.each do |element|
      element - 1
    end
  end
end

结果:

                user     system      total        real
with if     0.020000   0.000000   0.020000 (  0.018115)
without if  0.010000   0.000000   0.010000 (  0.012248)

在一个包含 100,000 个元素的数组中,这只是大约 0.006 秒的差异。除非它成为瓶颈,否则您不必担心它,而我怀疑它会成为瓶颈。


1
a = ['a', 'b', 'c']
a.each_with_index.reject {|el,i| i == 1}.each do |el,i|
  # do whatever with the element
  puts el
end

在编程中,使用 IMHO 而不是自己的显式 if 语句可能是更好的选择。然而我相信,这将导致与使用 if 相同甚至稍微低一些的性能。

如果按照其他人建议进行基准测试后,您知道它所需的时间肯定比您允许的要慢,并且选择是导致缓慢的原因,那么可以通过多种方式轻松修改以删除选择:

a = ['a', 'b', 'c']
n = 1
(a.first(n) + a.drop(n + 1)).each do |el|
  # do whatever with the element
  puts el
end

不幸的是,我认为这种方法比简单的if语句运行得更慢。我认为可能有潜力提高速度的方法是:

a = ['a', 'b', 'c']
n = 1
((0...n).to_a+((n+1)...a.size).to_a).map{|i| a[i]}.each do |el|
  # do whatever with the element
  puts el
end

但这样做的可能性很高会更慢。

编辑

基准测试在this gist中。这些结果实际上让我感到惊讶,拒绝选项是迄今为止最慢的选项,其次是范围。在根本不删除元素的情况下表现最好的方法是使用firstdrop来选择周围的所有元素。

使用无选择作为基线的百分比结果:

with if             146%
with first and drop 104%
without if          100%

显然,这高度取决于您对元素的操作,这是使用Ruby可能执行的最快操作进行测试的。 操作越慢,它们之间的差异就越小。 像往常一样:基准测试,基准测试,基准测试


1

测试一个真正的for循环可能值得花费五分钟的时间。在Ruby圈子里可能会有所不同,但这并不意味着它从来没有价值。当您调用每个或映射或其他任何方法时,那些方法无论如何都使用for循环。避免绝对化。

这也取决于数组可以变得多大,在某些n下,其中一个可能比另一个更快。在这种情况下,绝对不值得。

如果您不需要特定元素,则可能不需要在数据库中存储该数据行。第1行和其余行之间有什么区别,换句话说,为什么要跳过它?具有id = 1的行始终具有相同的数据吗?如果是这样,将其存储为常量可能更好,并且会使您的问题无关紧要。性能几乎总是会占用更多的内存。

除非Rails 3采用不同的方式,您提取数据并使用id作为查找器键,id = 1将位于元素0中。

不幸的是,Knuth 的话经常被误解,并被用来为那些本应该避免写出的低效代码辩护,这些代码只要程序员足够受过教育并花费 5 秒钟思考就不会存在。当然,如果你不知道代码存在问题或者问题很小,花一周时间尝试加速它也是有问题的,但这更接近 Knuth 所说的内容。性能是计算机科学中最被误解和滥用的概念之一。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接