【问题标题】:How do I copy a hash in Ruby?如何在 Ruby 中复制哈希?
【发布时间】:2011-05-08 15:05:43
【问题描述】:

我承认我是一个红宝石新手(现在正在编写 rake 脚本)。在大多数语言中,复制构造函数很容易找到。搜索了半个小时,没有在ruby中找到。我想创建哈希的副本,以便可以在不影响原始实例的情况下对其进行修改。

一些未按预期工作的预期方法:

h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1=Hash.new(h0)
h2=h1.to_hash

与此同时,我采用了这种不优雅的解决方法

def copyhash(inputhash)
  h = Hash.new
  inputhash.each do |pair|
    h.store(pair[0], pair[1])
  end
  return h
end

【问题讨论】:

  • 如果您正在处理普通的 Hash 对象,则提供的答案很好。如果您正在处理来自您无法控制的地方的类似 Hash 的对象,您应该考虑是否希望与 Hash 关联的单例类重复或不。见stackoverflow.com/questions/10183370/…

标签: ruby serialization hashmap copy deep-copy


【解决方案1】:

使用Object#clone:

h1 = h0.clone

(令人困惑的是,clone 的文档说 initialize_copy 是覆盖它的方法,但 Hash 中该方法的链接将您定向到 replace...)

【讨论】:

    【解决方案2】:

    clone 方法是 Ruby 的标准内置方法,用于执行 shallow-copy

    irb(main):003:0> h0 = {"John" => "Adams", "Thomas" => "Jefferson"}
    => {"John"=>"Adams", "Thomas"=>"Jefferson"}
    irb(main):004:0> h1 = h0.clone
    => {"John"=>"Adams", "Thomas"=>"Jefferson"}
    irb(main):005:0> h1["John"] = "Smith"
    => "Smith"
    irb(main):006:0> h1
    => {"John"=>"Smith", "Thomas"=>"Jefferson"}
    irb(main):007:0> h0
    => {"John"=>"Adams", "Thomas"=>"Jefferson"}
    

    请注意,该行为可能会被覆盖:

    此方法可能具有特定于类的行为。如果是这样,该行为将记录在该类的 #initialize_copy 方法下。

    【讨论】:

    • 克隆是对象上的一个方法,顺便说一句,所以一切都可以访问它。查看 API 详情here
    • 在这里为那些没有阅读其他答案的人添加更明确的评论,这是一个浅拷贝。
    • #initialize_copy 文档似乎不存在哈希,即使哈希文档页面ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy上有指向它的链接
    • 而对于其他 Ruby 初学者来说,“浅拷贝”意味着第一层以下的每个对象仍然是一个引用。
    • 请注意,这对我来说不适用于嵌套哈希(如其他答案中所述)。我用Marshal.load(Marshal.dump(h))
    【解决方案3】:

    正如其他人指出的那样,clone 会这样做。请注意,哈希的clone 会生成浅拷贝。也就是说:

    h1 = {:a => 'foo'} 
    h2 = h1.clone
    h1[:a] << 'bar'
    p h2                # => {:a=>"foobar"}
    

    发生的是哈希的引用被复制,而不是引用引用的对象。

    如果你想要一个深拷贝,那么:

    def deep_copy(o)
      Marshal.load(Marshal.dump(o))
    end
    
    h1 = {:a => 'foo'}
    h2 = deep_copy(h1)
    h1[:a] << 'bar'
    p h2                # => {:a=>"foo"}
    

    deep_copy 适用于任何可以编组的对象。大多数内置数据类型(数组、哈希、字符串等)都可以编组。

    Marshalling 是 Ruby 对 serialization 的名称。通过编组,对象——连同它所指的对象——被转换为一系列字节;然后这些字节被用来创建另一个像原始对象一样的对象。

    【讨论】:

    • @K.Carpenter 不是共享部分原件的副本吗?深拷贝,据我了解,是不共享原件任何部分的副本,因此修改一个不会修改另一个。
    • Marshal.load(Marshal.dump(o)) 深度复制到底是怎样的?我真的无法理解幕后发生的事情
    • 这也强调了如果你做h1[:a] &lt;&lt; 'bar'你修改了原始对象(h1[:a]指向的字符串)但是如果你做h1[:a] = "#{h1[:a]}bar",你将创建一个新的字符串对象,并指向h1[:a],而h2[:a] 仍指向旧的(未修改的)字符串。
    • 注意:通过 Marshal 方法进行克隆会导致远程代码执行。 ruby-doc.org/core-2.2.0/…
    • @JesseAldridge 是的,如果Marshal.load 的输入不受信任,请记住一个很好的警告。在这种情况下,它的输入来自我们自己进程中的Marshal.dump。我认为Marshal.load 在这种情况下是安全的。
    【解决方案4】:

    您可以使用下面的方法来深度复制 Hash 对象。

    deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
    

    【讨论】:

    • 这是韦恩康拉德答案的副本。
    【解决方案5】:

    Hash 可以从现有的散列创建一个新的散列:

    irb(main):009:0> h1 = {1 => 2}
    => {1=>2}
    irb(main):010:0> h2 = Hash[h1]
    => {1=>2}
    irb(main):011:0> h1.object_id
    => 2150233660
    irb(main):012:0> h2.object_id
    => 2150205060
    

    【讨论】:

    • 请注意,这与 #clone 和 #dup 具有相同的深层复制问题。
    • @forforf 是正确的。如果您不了解深拷贝和浅拷贝,请不要尝试复制数据结构。
    【解决方案6】:

    这是一种特殊情况,但如果您从一个预定义的散列开始,想要抓取并复制它,您可以创建一个返回散列的方法:

    def johns 
        {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
    end
    
    h1 = johns
    

    我遇到的特殊情况是我有一组 JSON 模式哈希,其中一些哈希建立在其他哈希之上。我最初将它们定义为类变量并遇到了这个复制问题。

    【讨论】:

      【解决方案7】:

      由于 Ruby 有一百万种方法可以做到这一点,这里有另一种使用 Enumerable 的方法:

      h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
      h1 = h0.inject({}) do |new, (name, value)| 
          new[name] = value;
          new 
      end
      

      【讨论】:

        【解决方案8】:

        由于标准克隆方法保留了冻结状态,因此不适合基于原始对象创建新的不可变对象,如果您希望新对象与原始对象略有不同(如果您喜欢无状态编程)。

        【讨论】:

          【解决方案9】:

          克隆很慢。为了提高性能,可能应该从空白哈希和合并开始。不包括嵌套哈希的情况...

          require 'benchmark'
          
          def bench  Benchmark.bm do |b|    
              test = {'a' => 1, 'b' => 2, 'c' => 3, 4 => 'd'}
              b.report 'clone' do
                1_000_000.times do |i|
                  h = test.clone
                  h['new'] = 5
                end
              end
              b.report 'merge' do
                1_000_000.times do |i|
                  h = {}
                  h['new'] = 5
                  h.merge! test
                end
              end
              b.report 'inject' do
                1_000_000.times do |i|
                  h = test.inject({}) do |n, (k, v)|
                    n[k] = v;
                    n
                  end
                  h['new'] = 5
                end
              end
            end
          end
          
          bench 用户系统总数(真实) 克隆 1.960000 0.080000 2.040000 (2.029604) 合并 1.690000 0.080000 1.770000 ( 1.767828) 注入 3.120000 0.030000 3.150000 ( 3.152627)

          【讨论】:

            【解决方案10】:

            对我有用的 Deep_Copy 的替代方法。

            h1 = {:a => 'foo'} 
            h2 = Hash[h1.to_a]
            

            这产生了一个 deep_copy,因为 h2 是使用 h1 的数组表示而不是 h1 的引用形成的。

            【讨论】:

            • 听起来很有希望但不起作用,这是另一个浅拷贝
            【解决方案11】:

            如果你使用 Rails,你可以这样做:

            h1 = h0.deep_dup
            

            http://apidock.com/rails/Hash/deep_dup

            【讨论】:

            • Rails 3 在哈希中存在 deep_duping 数组的问题。 Rails 4 解决了这个问题。
            • 感谢您指出这一点,使用 dup 或 clone 时我的哈希仍然受到影响
            【解决方案12】:

            我也是 Ruby 的新手,我在复制哈希时遇到了类似的问题。使用以下内容。我不知道这种方法的速度。

            copy_of_original_hash = Hash.new.merge(original_hash)
            

            【讨论】:

              【解决方案13】:

              正如Security Considerations section of Marshal documentation中提到的,

              如果您需要反序列化不受信任的数据,请使用 JSON 或其他 只能加载简单的“原始”的序列化格式 String、Array、Hash等类型

              这是一个关于如何在 Ruby 中使用 JSON 进行克隆的示例:

              require "json"
              
              original = {"John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
              cloned = JSON.parse(JSON.generate(original))
              
              # Modify original hash
              original["John"] << ' Sandler'
              p original 
              #=> {"John"=>"Adams Sandler", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"}
              
              # cloned remains intact as it was deep copied
              p cloned  
              #=> {"John"=>"Adams", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"}
              

              【讨论】:

              • 这在大多数情况下都有效,但如果您的键是整数而不是字符串,请注意。当您往返 JSON 时,键将变成字符串。
              猜你喜欢
              • 1970-01-01
              • 2016-11-14
              • 1970-01-01
              • 2011-12-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-04-09
              相关资源
              最近更新 更多