【问题标题】:Better way to initialize and update deeply nested Hash初始化和更新深度嵌套哈希的更好方法
【发布时间】:2018-11-01 06:06:41
【问题描述】:

我有一个Hash,我想在其中插入一些更深层次的数据,但是任何层次都可能缺少一个键。所以,我在每个级别更新它的值之前有条件地初始化它。

有什么更好的方法来编写这个或可以使代码不那么难看的方法?

data[:foo] ||= {}
data[:foo][:bar] ||= {}
data[:foo][:bar][:baz] ||= []
data[:foo][:bar][:baz] << 99

【问题讨论】:

    标签: ruby ruby-hash


    【解决方案1】:

    使用哈希自动生存

    data = Hash.new { |h, k| h[k] = h.dup.clear }
    #⇒ {}
    
    # or, credits to @Amadan:
    data = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
    #⇒ {}
    
    data[:foo][:bar][:baz] = 42
    data
    #⇒ {:foo=>{:bar=>{:baz=>42}}}
    

    这里使用的技巧是我们使用Hash#default_proc 创建嵌套键。

    对于您的情况:

    (data[:foo][:bar][:baz] = []) << 99
    

    【讨论】:

    • 数据[:foo][:bar][:baz] ||= [] => {:foo=>{:bar=>{:baz=>{}}}}跨度>
    • @BryanAsh 那又怎样?显然不应该使用default_proc 在哈希上调用||=,而是使用=
    • Hash.new { |h, k| h[k] = Hash.new(&amp;h.default_proc) } 效率更高一点。
    • 嗯,学分,好像我发明了它一样 :D 无论如何,这是一个众所周知的咒语,有一个鲜为人知的名字:“hash autovivification”。
    • 我想如果data 是一个给定的非空哈希,则只需附加一个默认过程。如果这样做会不会有副作用?例如,如果哈希已经有一个默认的过程怎么办?请注意,有必要使用h.key?(k) 来检查密钥是否存在,而不是依赖h[k].nil?
    【解决方案2】:

    你也可以这样做:

    class SpecialHash < Hash
      def [](key)
        if has_key?(key)
          super(key)
        else
          self[key] = self.class.new
        end
      end
    end
    
    h = SpecialHash.new
    h[:foo][:bar][:baz] = "Baz"
    h # => {:foo=>{:bar=>{:baz=>"Baz"}}}
    

    它的类型与Hash 完全相同。

    您可以重新格式化相同的代码:

    class SpecialHash < Hash
      def [](key)
        return super if has_key?(key)
        self[key] = self.class.new
      end
    end
    

    甚至

    class SpecialHash < Hash
      def [](key)
        has_key?(key) ? super : self[key] = self.class.new
      end
    end
    

    【讨论】:

    • 坏主意。永远不要为像这样微不足道的东西子类化内置集合类。你能猜到为什么吗?
    • 我以前听说过,但不记得为什么了。
    • h.slice(:foo)[:f1][:f2] = 'f3' 的结果是什么?
    • 是的,正是这个。
    • 甚至def [](key); super || store(key,self.class.new); end
    【解决方案3】:

    可以使用递归。

    def stuff_it(h, first_key, *rest_keys, val)
      if rest_keys.empty?
        (h[first_key] ||= []) << val
      else
        h[first_key] = stuff_it(h[first_key] ||= {}, *rest_keys, val)
      end
      h
    end   
    
    stuff_it({ a: 1 }, :foo, :bar, :baz, 99)
      #=> {:a=>1, :foo=>{:bar=>{:baz=>[99]}}}
    stuff_it({ a: 1, foo: { b: 2 } }, :foo, :bar, :baz, 99)
      #=> {:a=>1, :foo=>{:b=>2, :bar=>{:baz=>[99]}}}
    stuff_it({ a: 1, foo: { b: 2, bar: { c: 3 } } }, :foo, :bar, :baz, 99)
      #=> {:a=>1, :foo=>{:b=>2, :bar=>{:c=>3, :baz=>[99]}}}
    h = { a: 1, foo: { b: 2, bar: { c: 3, baz: [88] } } }
    stuff_it(h, :foo, :bar, :baz, 99)
      #=> {:a=>1, :foo=>{:b=>2, :bar=>{:c=>3, :baz=>[88, 99]}}}
    h # => {:a=>1, :foo=>{:b=>2, :bar=>{:c=>3, :baz=>[88, 99]}}}
    

    从上一个示例可以看出,该方法具有破坏性。只需稍加改动即可使其成为非破坏性的。

    def stuff_it(g, first_key, *rest_keys, val)
      h = g.merge(g)
      if rest_keys.empty?
         h[first_key] = h[first_key] ? h[first_key].dup << val : [val]
      else
        h[first_key] = stuff_it(h[first_key] ||= {}, *rest_keys, val)
      end
      h
    end   
    
    h = { a: 1, foo: { b: 2, bar: { c: 3 } } }
    stuff_it(h, :foo, :bar, :baz, 99)
      #=> {:a=>1, :foo=>{:b=>2, :bar=>{:c=>3, :baz=>[99]}}}
    h #=> { a: 1, foo: { b: 2, bar: { c: 3 } } }
    
    h = { a: 1, foo: { b: 2, bar: { c: 3, baz: [88] } } }
    stuff_it(h, :foo, :bar, :baz, 99)
      #=> {:a=>1, :foo=>{:b=>2, :bar=>{:c=>3, :baz=>[88, 99]}}}
    h #=> {:a=>1, :foo=>{:b=>2, :bar=>{:c=>3, :baz=>[88]}}}
    

    【讨论】:

    • 感谢您的回答。但根据我的需要,我想我现在会接受 mudasobwa 的回答。可能会晚一点。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 2019-03-22
    • 2023-04-04
    相关资源
    最近更新 更多