Ruby 嵌套哈希键的点符号

Ruby dot notation to nested hash keys

将点符号路径(甚至字符串数组)转换为嵌套哈希键值的最佳方法是什么?例如:我需要像这样将 'foo.bar.baz' 转换为 'qux'

{
    'foo' => {
        'bar' => {
            'baz' => 'qux'
        }
    }
}

我在 PHP 中完成了此操作,但我通过在数组中创建一个键然后通过引用将 tmp 变量设置为该数组键的值来管理,因此任何更改也会发生在数组。

我可能会使用递归。例如:

def hasherizer(arr, value)
  if arr.empty?
    value
  else
    {}.tap do |hash|
      hash[arr.shift] = hasherizer(arr, value)
    end
  end
end

这导致:

> hasherizer 'foo.bar.baz'.split('.'), 'qux'
 => {"foo"=>{"bar"=>{"baz"=>"qux"}}}

试试这个

f = "root/sub-1/sub-2/file"   
f.split("/").reverse.inject{|a,n| {n=>a}} #=>{"root"=>{"sub-1"=>{"sub-2"=>"file"}}}

当我编写一个 HTTP 服务器时,我做了类似的事情,该服务器必须将请求中传递的所有参数移动到一个可能包含数组或字符串或哈希的多值哈希中...

您可以查看 Plezi server and framework... 的代码,尽管那里的代码处理的是 []...

包围的值

可以这样调整:

def add_param_to_hash param_name, param_value, target_hash = {}
    begin
        a = target_hash
        p = param_name.split(/[\/\.]/)
        val = param_value
        # the following, somewhat complex line, runs through the existing (?) tree, making sure to preserve existing values and add values where needed.
        p.each_index { |i| p[i].strip! ; n = p[i].match(/^[0-9]+$/) ? p[i].to_i : p[i].to_sym ; p[i+1] ? [ ( a[n] ||= ( p[i+1].empty? ? [] : {} ) ), ( a = a[n]) ] : ( a.is_a?(Hash) ? (a[n] ? (a[n].is_a?(Array) ? (a << val) : a[n] = [a[n], val] ) : (a[n] = val) ) : (a << val) ) }
    rescue Exception => e
        warn "(Silent): parameters parse error for #{param_name} ... maybe conflicts with a different set?"
        target_hash[param_name] = param_value
    end
end

这应该保留现有值,同时添加新值(如果存在)。

长线分解后看起来像这样:

def add_param_to_hash param_name, param_value, target_hash = {}
    begin
        # a will hold the object to which we Add.
        # As we walk the tree we change `a`. we start at the root...
        a = target_hash
        p = param_name.split(/[\/\.]/)
        val = param_value
        # the following, somewhat complex line, runs through the existing (?) tree, making sure to preserve existing values and add values where needed.
        p.each_index do |i|
             p[i].strip!
             # converts the current key string to either numbers or symbols... you might want to replace this with: n=p[i]
             n = p[i].match(/^[0-9]+$/) ? p[i].to_i : p[i].to_sym
             if p[i+1]
                   a[n] ||= ( p[i+1].empty? ? [] : {} ) # is the new object we'll add to
                   a = a[n] # move to the next branch.
             else
                 if a.is_a?(Hash)
                    if a[n]
                       if a[n].is_a?(Array)
                            a << val
                       else
                            a[n] = [a[n], val]
                       end
                    else
                       a[n] = val
                    end
                 else
                    a << val
                 end
             end
        end
    rescue Exception => e
        warn "(Silent): parameters parse error for #{param_name} ... maybe conflicts with a different set?"
        target_hash[param_name] = param_value
    end
end

Brrr...看着这样的代码,我不知道我在想什么...

我喜欢下面这种对自身(或您自己的散列 class )进行操作的方法。它将创建新的哈希键或 reuse/append 到哈希中的现有键以添加或更新值。

# set a new or existing nested key's value by a dotted-string key
  def dotkey_set(dottedkey, value, deep_hash = self)
    keys = dottedkey.to_s.split('.')
    first = keys.first
    if keys.length == 1
      deep_hash[first] = value
    else
      # in the case that we are creating a hash from a dotted key, we'll assign a default
      deep_hash[first] = (deep_hash[first] || {})
      dotkey_set(keys.slice(1..-1).join('.'), value, deep_hash[first])
    end
  end

用法:

hash = {}

hash.dotkey_set('how.are.you', 'good')
# => "good"
hash
# => {"how"=>{"are"=>{"you"=>"good"}}}

hash.dotkey_set('how.goes.it', 'fine')
# => "fine"
hash
# => {"how"=>{"are"=>{"you"=>"good"}, "goes"=>{"it"=>"fine"}}}