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

时间:2017-08-01 09:17:27

标签: ruby-on-rails ruby hash

我有一个哈希,比方说,

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

现在我想将account转换为这样的哈希:

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

我是初学者,想知道是否有任何功能可以做到这一点? (除了合并嵌套哈希然后删除它)

3 个答案:

答案 0 :(得分:3)

您可以实现一种通用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

在这里,我们仍在执行删除/合并序列,但无论如何它都需要在任何此类实现中,即使隐藏在进一步的抽象之下也是如此。

您可以按如下方式使用此方法:

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"}

请注意,使用此方法时,默认情况下,较低级别哈希中的任何键都会覆盖上级哈希中的现有键。但是,您可以提供一个块来解决任何合并冲突。请参阅documentation of Hash#merge!了解如何使用此功能。

答案 1 :(得分:1)

这样可以解决问题:

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

答案 2 :(得分:0)

案例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"}

案例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"}

案例1的解释

计算进展如下。

在这里使用Enumerable#flat_map的一种方法是,对于某些方法g

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

其中abcfgh都是数组,那么

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

让我们首先创建一个枚举器,将元素传递给块。

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

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>