Converting a nested hash into a flat hash

2020-01-23 11:42发布

This question is the inverse of this question.

Given a nested hash like

{
    :a => {
       :b => {:c => 1, :d => 2},
       :e => 3,
    },
    :f => 4,
}

what is the best way to convert it into a flat hash like

{
    [:a, :b, :c] => 1,
    [:a, :b, :d] => 2,
    [:a, :e] => 3,
    [:f] => 4,
}

标签: ruby hash
8条回答
爱情/是我丢掉的垃圾
2楼-- · 2020-01-23 12:24

A declarative solution using DeepEnumerable:

require 'deep_enumerable'

h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }

h.deep_each.map do |k, v|
  [DeepEnumerable.deep_key_to_array(k), v]
end.to_h

or, for those who prefer point-free style

h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array))
查看更多
放我归山
3楼-- · 2020-01-23 12:24

Array support / readable names / no update for speed / stringified results keys

def flat_hash(input, base = nil, all = {})
  if input.is_a?(Array)
    input = input.each_with_index.to_a.each(&:reverse!)
  end

  if input.is_a?(Hash) || input.is_a?(Array)
    input.each do |k, v|
      flat_hash(v, base ? "#{base}.#{k}" : k, all)
    end
  else
    all[base] = input
  end

  all
end
查看更多
啃猪蹄的小仙女
4楼-- · 2020-01-23 12:28

Very similar to Adiel Mittmann's solution

def flat_hash(h, k = [])
  new_hash = {}
  h.each_pair do |key, val|
    if val.is_a?(Hash)
      new_hash.merge!(flat_hash(val, k + [key]))
    else
      new_hash[k + [key]] = val
    end
  end
  new_hash
end

Edit: Refactored for elegance. Should be almost as fast.

def flat_hash(hash, k = [])
  return {k => hash} unless hash.is_a?(Hash)
  hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
查看更多
Bombasti
5楼-- · 2020-01-23 12:28

Inspired by @cary-swoveland way, but in Hash class :

class Hash
  def deep_flatten(previous_key=[])
    flat_hash = {}
    self.each do |key, value|
      next_key = previous_key+[key]
      flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value})
    end
    return flat_hash
  end
end

h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }

h.deep_flatten #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
查看更多
ゆ 、 Hurt°
6楼-- · 2020-01-23 12:29

My attempt:

def flatten_hash(h)
  return { [] => h } unless h.is_a?(Hash)
  Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)]
end

Sorry for the bad variables names, had to fit it in one line.

查看更多
啃猪蹄的小仙女
7楼-- · 2020-01-23 12:38

This is not an attempt to give you the best way to do it, but it is a way :P

def flatten(hash)
  return {[] => hash} if !hash.is_a?(Hash)
  map = {}
  hash.each_pair do |key1, value1|
    flatten(value1).each_pair do |key2, value2|
      map[[key1] + key2] = value2
    end
  end
  return map
end

It works for your example, producing this result:

{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}

It may not produce the result you expect if there are empty hashes.

查看更多
登录 后发表回答