如何在 Ruby 中复制哈希?

Posted

技术标签:

【中文标题】如何在 Ruby 中复制哈希?【英文标题】:How do I copy a hash in 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 关联的单例类重复或不。见***.com/questions/10183370/… 【参考方案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 时,键将变成字符串。

以上是关于如何在 Ruby 中复制哈希?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Ruby 的 bcrypt 库在哈希中包含明文中的盐? [复制]

如何在Ruby中使用来自哈希的查询参数构造URI

如何在 Ruby 中创建对象的深层副本?

在更改 ruby​​ 中的某些值后,如何维护具有原始值的哈希副本?

Ruby Style:如何检查嵌套的哈希元素是不是存在

Ruby Style:如何检查嵌套的哈希元素是不是存在