简体   繁体   English

将内部哈希中的键转换为外部/父哈希中的键

[英]Convert keys in inner hash to keys of outer/parent hash

I have a hash, say, 我有一个哈希,说,

account = { 
  name: "XXX", 
  email: "xxx@yyy.com", 
  details: { 
    phone: "9999999999", 
    dob: "00-00-00", 
    address: "zzz" 
  } 
}

Now I want to convert account to a hash like this: 现在,我想将account转换为像这样的哈希:

account = { 
  name: "XXX", 
  email: "xxx@yyy.com", 
  phone: "9999999999", 
  dob: "00-00-00", 
  address: "zzz"
}

I'm a beginner and would like to know if there is any function to do it? 我是一个初学者,想知道是否有任何功能可以执行? (Other than merging the nested hash and then deleting it) (除了合并嵌套的哈希,然后将其删除)

You could implement a generic flatten_hash method which works roughly similar to Array#flatten in that it allows to flatten Hashes of arbitrary depth. 您可以实现一个通用的flatten_hash方法,该方法的工作原理与Array#flatten大致相似,因为它可以展平任意深度的哈希。

def flatten_hash(hash, &block)
  hash.dup.tap do |result|
    hash.each_pair do |key, value|
      next unless value.is_a?(Hash)

      flattened = flatten_hash(result.delete(key), &block) 
      result.merge!(flattened, &block)
    end
  end
end

Here, we are still performing the delete / merge sequence, but it would be required in any such implementation anyway, even if hidden below further abstractions. 在这里,我们仍在执行删除/合并序列,但是无论如何,即使隐藏在进一步的抽象之下,它仍然是必需的。

You can use this method as follows: 您可以按以下方式使用此方法:

account = { 
  name: "XXX", 
  email: "xxx@yyy.com", 
  details: { 
    phone: "9999999999", 
    dob: "00-00-00", 
    address: "zzz" 
  } 
}

flatten(account)
# => {:name=>"XXX", :email=>"xxx@yyy.com", :phone=>"9999999999", :dob=>"00-00-00", :address=>"zzz"}

Note that with this method, any keys in lower-level hashes overwrite existing keys in upper-level hashes by default. 请注意,使用此方法时,默认情况下,低层哈希中的任何键都会覆盖高层哈希中的现有键。 You can however provide a block to resolve any merge conflicts. 但是,您可以提供一个块来解决任何合并冲突。 Please refer to the documentation of Hash#merge! 请参考Hash#merge!文档Hash#merge! to learn how to use this. 学习如何使用它。

这将达到目的:

account.map{|k,v| k==:details ? v : {k => v}}.reduce({}, :merge)

Case 1: Each value of account may be a hash whose values are not hashes 情况1:每个account值可能是一个哈希值,其值不是哈希值

account.flat_map { |k,v| v.is_a?(Hash) ? v.to_a : [[k,v]] }.to_h
  #=> {:name=>"XXX", :email=>"xxx@yyy.com", :phone=>"9999999999",
  #    :dob=>"00-00-00", :address=>"zzz"}

Case 2: account may have nested hashes 情况2: account可能有嵌套的哈希

def doit(account)
  recurse(account.to_a).to_h
end

def recurse(arr)
  arr.each_with_object([]) { |(k,v),a|
    a.concat(v.is_a?(Hash) ? recurse(v.to_a) : [[k,v]]) }
end

account = { 
  name: "XXX", 
  email: "xxx@yyy.com", 
  details: { 
    phone: "9999999999", 
    dob: { a: 1, b: { c: 2, e: { f: 3 } } },
    address: "zzz" 
  } 
}

doit account
  #=> {:name=>"XXX", :email=>"xxx@yyy.com", :phone=>"9999999999", :a=>1,
  # :c=>2, :f=>3, :address=>"zzz"}

Explanation for Case 1 情况1的说明

The calculations progress as follows. 计算过程如下。

One way to think of Enumerable#flat_map , as it is used here, is that if, for some method g , 考虑此处使用的Enumerable#flat_map的一种方法是,如果对于某些方法g

[a, b, c].map { |e| g(e) } #=> [f, g, h]

where a , b , c , f , g and h are all arrays, then 其中abcfgh都是数组,则

[a, b, c].flat_map { |e| g(e) } #=> [*f, *g, *h]

Let's start by creating an enumerator to pass elements to the block. 让我们从创建一个枚举器开始,将元素传递给该块。

enum = account.to_enum
  #=> #<Enumerator: {:name=>"XXX", :email=>"xxx@yyy.com",
  #     :details=>{:phone=>"9999999999", :dob=>"00-00-00",
  #     :address=>"zzz"}}:each>

enum generates an element which is passed to the block and the block variables are set equal to those values. enum生成一个元素,该元素传递到块,并且块变量设置为等于那些值。

k, v = enum.next
  #=> [:name, "XXX"]
k #=> :name
v #=> "XXX"
v.is_a?(Hash)
  #=> false
a = [[k,v]]
  #=> [[:name, "XXX"]]

k, v = enum.next
  #=> [:email, "xxx@yyy.com"]
v.is_a?(Hash)
  #=> false
b = [[k,v]]
  #=> [[:email, "xxx@yyy.com"]]

k,v = enum.next
  #=> [:details, {:phone=>"9999999999", :dob=>"00-00-00", :address=>"zzz"}]
v.is_a?(Hash)
  #=> true
c = v.to_a
  #=> [[:phone, "9999999999"], [:dob, "00-00-00"], [:address, "zzz"]]

d = account.flat_map { |k,v| v.is_a?(Hash) ? v.to_a : [[k,v]] }
  #=> [*a, *b, *c]
  #=> [[:name, "XXX"], [:email, "xxx@yyy.com"], [:phone, "9999999999"],
  #    [:dob, "00-00-00"], [:address, "zzz"]]

d.to_h
  #=> <the return value shown above>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM