Ruby中是否有内置的懒惰哈希?
我需要使用各种值填充Hash.有些值经常被访问,而另一些值很少被访问.
问题是,我正在使用一些计算来获取值,并且使用多个键填充哈希变得非常慢. 在我的情况下,使用某种缓存不是一种选择. 我想知道如何只在首次访问密钥而不是添加密钥时才使哈希计算值? 这样,很少使用的值不会减慢填充过程. 我正在寻找“有点异步”或懒惰访问的东西. 解决方法
有很多不同的方法来解决这个问题.我建议使用您定义的类的实例而不是哈希.例如,而不是……
# Example of slow code using regular Hash. h = Hash.new h[:foo] = some_long_computation h[:bar] = another_long_computation # Access value. puts h[:foo] …制作自己的类并定义方法,像这样…… class Config def foo some_long_computation end def bar another_long_computation end end config = Config.new puts config.foo 如果你想要一个简单的方法来缓存长计算,或者它绝对必须是一个Hash,而不是你自己的类,你现在可以用一个Hash包装Config实例. config = Config.new h = Hash.new {|h,k| h[k] = config.send(k) } # Access foo. puts h[:foo] puts h[:foo] # Not computed again. Cached from previous access. 上面示例的一个问题是h.keys不会包含:bar,因为您尚未访问它.因此,您无法迭代h中的所有键或条目,因为它们在实际访问之前不存在.另一个潜在的问题是您的密钥需要是有效的Ruby标识符,因此在Config上定义时,任意带空格的String键都不起作用. 如果这对您很重要,有不同的方法来处理它.您可以这样做的一种方法是使用thunks填充哈希值并在访问时强制使用thunk. class HashWithThunkValues < Hash def [](key) val = super if val.respond_to?(:call) # Force the thunk to get actual value. val = val.call # Cache the actual value so we never run long computation again. self[key] = val end val end end h = HashWithThunkValues.new # Populate hash. h[:foo] = ->{ some_long_computation } h[:bar] = ->{ another_long_computation } h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier. # Access hash. puts h[:foo] puts h[:foo] # Not computed again. Cached from previous access. puts h.keys #=> [:foo,:bar,"invalid Ruby name"] 最后一个示例的一个警告是,如果您的值是可调用的,它将无法工作,因为它无法区分需要强制的thunk和值之间的区别. 同样,有办法处理这个问题.一种方法是存储一个标记是否已评估值的标志.但这需要为每个条目留出额外的内存.更好的方法是定义一个新类来标记Hash值是未评估的thunk. class Unevaluated < Proc end class HashWithThunkValues < Hash def [](key) val = super # Only call if it's unevaluated. if val.is_a?(Unevaluated) # Force the thunk to get actual value. val = val.call # Cache the actual value so we never run long computation again. self[key] = val end val end end # Now you must populate like so. h = HashWithThunkValues.new h[:foo] = Unevaluated.new { some_long_computation } h[:bar] = Unevaluated.new { another_long_computation } h["invalid Ruby name"] = Unevaluated.new { a_third_computation } # Some key that's an invalid ruby identifier. h[:some_proc] = Unevaluated.new { Proc.new {|x| x + 2 } } 这样做的缺点是,现在你必须记住在填充你的哈希时使用Unevaluted.new.如果您希望所有值都是惰性的,您也可以覆盖[] =.我认为它实际上不会节省太多的输入,因为你仍然需要使用Proc.new,proc,lambda或 – > {}来创建块.但这可能是值得的.如果你这样做,它可能看起来像这样. class HashWithThunkValues < Hash def []=(key,val) super(key,val.respond_to?(:call) ? Unevaluated.new(&val) : val) end end 所以这是完整的代码. class HashWithThunkValues < Hash # This can be scoped inside now since it's not used publicly. class Unevaluated < Proc end def [](key) val = super # Only call if it's unevaluated. if val.is_a?(Unevaluated) # Force the thunk to get actual value. val = val.call # Cache the actual value so we never run long computation again. self[key] = val end val end def []=(key,val.respond_to?(:call) ? Unevaluated.new(&val) : val) end end h = HashWithThunkValues.new # Populate. h[:foo] = ->{ some_long_computation } h[:bar] = ->{ another_long_computation } h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier. h[:some_proc] = ->{ Proc.new {|x| x + 2 } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |