Ruby类变量以意想不到的方式表现

时间:2015-05-27 20:13:08

标签: ruby

我有一个收集所有实例变量的类变量。它还有一种获取和删除它们的方法。

module Somemodule
  class Widget
    @@widgets = []
    def initialize
      ...
      @@widgets << self
    end
    def self.all
      @@widgets
    end
    def delete
      @@widgets.delete(self)
    end
  end
end

在我的代码中,我想有时删除小部件。

module Somemodule
  def self.delete_bad_widgets
    Widget.all.each do |widget|
      if widget.bad
        widget.delete
      end
    end
  end
end

问题是这不会删除所有错误的小部件。如果我之后调用Widget.all,我仍然可以看到坏的小部件。任何人都知道发生了什么事吗?我正在运行Ruby 2.2.1。

谢谢

编辑:总而言之,大约有4500个小部件,代码会改变它们几次。但是我仍然可以运行类似下面的内容(在相同的delete_bad_widgets方法中!)并且仍然会看到错误的小部件:

Widget.all.each {|w| pp w if w.bad}

2 个答案:

答案 0 :(得分:3)

如果在每个条目中删除条目,则会影响循环。

请看这个例子:

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

p a
a.each{|x|
  p x
  a.delete(x) if x == 3
}
p a

结果:

  [1, 2, 3, 4, 5, 6, 7]
  1
  2
  3
  5
  6
  7
  [1, 2, 4, 5, 6, 7]

不打印条目4! 每个循环保留索引,但序列随删除而改变。

我建议实现类方法delete_baddelete_if

答案 1 :(得分:2)

尝试使用reject!删除不良小部件,而不是自己进行迭代。

module Somemodule
  def self.delete_bad_widgets
    Widget.all.reject! {|widget| widget.bad}
  end
end

正如knut的回答所指出的那样,在遍历数组时使用delete会导致某些元素被跳过。在迭代该集合的同时插入/删除集合的元素通常不是一个好主意。

此外,使用reject!(又名delete_if)会表现得更好,因为delete将在每次调用时基本遍历整个数组(即reject!给出O(n)表现在哪里 - 您使用它的方式 - delete将给出O(n ^ 2)最差情况表现。)