加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Ruby中是否有内置的懒惰哈希?

发布时间:2020-12-17 04:00:42 所属栏目:百科 来源:网络整理
导读:我需要使用各种值填充Hash.有些值经常被访问,而另一些值很少被访问. 问题是,我正在使用一些计算来获取值,并且使用多个键填充哈希变得非常慢. 在我的情况下,使用某种缓存不是一种选择. 我想知道如何只在首次访问密钥而不是添加密钥时才使哈希计算值? 这样,很
我需要使用各种值填充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 } }

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读