在特定位置的Ruby哈希中插入条目

时间:2013-06-21 13:42:28

标签: ruby

给出一个散列,例如,嵌套散列:

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1", 
                     "key2" => "val2"}}

和String格式的键的路径:

path = "nested.key2"

如何在key2条目之前添加新的键值对? 所以,预期的输出应该是这样的:

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1",
                    "new_key" => "new_value"},
                    "key2" => "val2"}}

EDITED

我的目标是在某个键之前添加一种标签,以便将哈希转储为Yaml文本,并对文本进行后处理以用Yaml注释替换添加的键/值。 AFAIK,没有其他方法可以通过编程方式在YAML中的特定键之前添加注释。

4 个答案:

答案 0 :(得分:7)

使用Hash的数组表示最简单:

subhash   = hash['nested'].to_a
insert_at = subhash.index(subhash.assoc('key2'))
hash['nested'] = Hash[subhash.insert(insert_at, ['new_key', 'new_value'])]

它可以包装成一个函数:

class Hash
  def insert_before(key, kvpair)
    arr = to_a
    pos = arr.index(arr.assoc(key))
    if pos
      arr.insert(pos, kvpair)
    else
      arr << kvpair
    end
    replace Hash[arr]
  end
end

hash['nested'].insert_before('key2', ['new_key', 'new_value'])

p hash # {"some_key"=>"value", "nested"=>{"key1"=>"val1", "new_key"=>"new_value", "key2"=>"val2"}}

答案 1 :(得分:2)

我经常为应用程序的大型配置创建YAML生成器。为了维护,我需要对字段进行排序。

我在按排序顺序生成YAML时使用的解决方案是根据需要添加密钥,以便将它们放入正确的哈希或子哈希中。然后我通过对键/值对进行排序来创建一个新的哈希,并对其进行to_yaml

没有必要对散列进行排序,但是在让YAML使用它之前对要输出的临时散列进行排序是有效的,并且会导致更容易维护的文件。

require 'yaml'

some_hash = {
    'z' => 1,
    'a' => 3
}

puts some_hash.to_yaml

哪个输出:

---
z: 1
a: 3

在创建YAML输出之前对其进行排序:

puts Hash[some_hash.merge('x' => 2).sort_by{ |k, v| k }].to_yaml

输出:

---
a: 3
x: 2
z: 1

使用puts代替File.write,或将该行嵌入传递给File.open的块中。


关于YAML文件中的注释:YAML不支持以编程方式向发出的输出添加注释。注释适用于人类,#不能映射到Ruby变量或对象。可以这样想:如果我们在名为test.yaml的文件中使用此YAML:

---
# string
a: 'fish'
# array
b: 
  - 1
  - 2
# hash
c: 
  d: 'foo'
  e: 'bar'
# integer
z: 1

加载它:

require 'pp'
require 'yaml'

obj = YAML.load_file('test.yaml')

pp obj

我看起来像obj

{"a"=>"fish", "b"=>[1, 2], "c"=>{"d"=>"foo", "e"=>"bar"}, "z"=>1}

没有返回“comment”对象,并且Ruby中不存在任何适合哈希的东西,它存在于YAML规范中。我们可以随意拼凑一个我们称之为Comment的类,并尝试将它作为键嵌入到对象中,但是YAML不会接受它作为注释,因为规范不允许它。它会将它定义为Ruby类并将其重新创建为该类,但它不会显示为#注释:

require 'yaml'

class Comment
  def initialize(some_text)
    @comment = "# #{some_text}"
  end
end

some_hash = {
  'a' => 1,
  Comment.new('foo') => 'bar',
  'z' => 'z'
}

puts some_hash.to_yaml

对外输出:

---
a: 1
? !ruby/object:Comment
  comment: ! '# foo'
: bar
z: z

当我在我发出的YAML配置中需要注释时,我会手动调整它们以便稍后添加它们。对于您想要做的事情,我建议使用您可以在文档中扫描的更多助记符或唯一变量名称,而不是进行任何手动调整。你甚至可以放入虚假的条目,除了作为占位符之外,不提供任何有价值的东西:

require 'yaml'

some_hash = {
  'a' => 1,
  '__we_are_here__' => '',
  'b' => 2,
  '__we_are_now_here__' => '',
  'z' => 'z'
}

puts some_hash.to_yaml

导致YAML文件如:

---
a: 1
__we_are_here__: ''
b: 2
__we_are_now_here__: ''
z: z

至于将密钥插入哈希,我可能会稍微重构我的“密钥链”,以显示我想要插入它的路径,以及新密钥的名称。同样,在保存YAML之前,我依靠排序来确保事情的顺序正确:

require 'pp'

# this changes the incoming hash
def insert_embedded_hash_element(hash, key_path, new_value)

  keys = key_path.split('.')
  new_key = keys.pop

  sub_hash = hash
  keys.each do |k|
    sub_hash = sub_hash[k]
  end

  sub_hash[new_key] = new_value

end

# the sub-hash to insert into + new key name
insert_key = 'nested.key2'
insert_value = 'new_value'

hash = {
  "some_key" => "value",
  "nested" => {
    "key1" => "val1", 
    "key3" => "val2"
  }
}

insert_embedded_hash_element(hash, insert_key, insert_value)

pp hash

导致:

{"some_key"=>"value",
 "nested"=>{"key1"=>"val1", "key3"=>"val2", "key2"=>"new_value"}}

答案 2 :(得分:2)

这只是OP的需要,但可以根据需要随时修改:

require 'yaml'

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1", 
                     "key2" => "val2"}}

new_hash = %w(nested key2).inject(hash) do |h,i|
 next h[i] unless h.has_key? "key2"
 ind = h.to_a.index{|m| m[0] == i }
 Hash[h.to_a.insert(ind,["new_key","new_value"])]
end

hash["nested"] = new_hash # this part is to be taken care of for deep hash.
puts hash.to_yaml

输出:

some_key: value
nested:
  key1: val1
  new_key: new_value
  key2: val2

更新:

我找到了更高效的代码,这将减少在我之前的代码中处理行hash["nested"] = new_hash的开销:

require 'yaml'

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1", 
                     "key2" => "val2"}}

new_hash = %w(nested key2).inject(hash) do |h,i| # !> assigned but unused variable - new_hash
 next h[i] unless h.has_key? "key2"
 ind = h.to_a.index{|m| m[0] == i }
 h1 = Hash[h.to_a.insert(ind,["new_key","new_value"])]
 h.replace(h1)
end

hash
# => {"some_key"=>"value",
#     "nested"=>{"key1"=>"val1", "new_key"=>"new_value", "key2"=>"val2"}}

puts hash.to_yaml
# >> ---
# >> some_key: value
# >> nested:
# >>   key1: val1
# >>   new_key: new_value
# >>   key2: val2

答案 3 :(得分:0)

我不认为ruby免费提供此功能。您可以执行以下操作,在此处创建现有哈希键的数组,将新键插入到数组中,然后使用新排序的键创建新哈希。

keys = original_hash.keys
keys.insert(new_key_position, new_key)

new_hash = {}
keys.each do |key|
  new_hash[key] = key == new_key ? new_value : original_hash[key]
end