2017-12-27 338 views
-1

我有一个嵌套的散列/阵列,例如:如何访问嵌套散列/数组中的元素,给定类似“a.b.0.x”的路径?

target = { 
    :a => { 
    "b" => [ 
     { 
     :x => "here" 
     } 
    ] 
    } 
} 

其中散列键是从来没有的数字。给定一串由"."(例如"a.b.0.x")表示的target[:a]["b"][0][:x]之间的索引/键,我如何访问相应的元素?

+1

问题尚不清楚。你怎么知道字符串中的“a”部分是为了“:a”还是“a”?你怎么知道'0'是否为'0','“0”'或':0'? – sawa

+2

我不认为这是很好的编码口味。 – Daniel

+0

@Daniel为什么呢?它是例如完全有效的快速调整巨大的配置。作为用户_,我当然会感谢开发人员通过点符号提供了对设置的访问权限。 – mudasobwa

回答

4
path = "a.b.0.x" 

path.split('.').reduce(target) do |acc, val| 
    case acc 
    when Array 
    break nil unless /\A\d+\z/ === val 
    acc[val.to_i] 
    when Hash 
    next acc[val.to_i] if /\A\d+\z/ === val && acc[val.to_i] 
    acc[val] || acc[val.to_sym] 
    else break nil 
    end 
end rescue nil 
#⇒ "here" 
+1

鉴于rails:'target.with_indifferent_access.dig (* path.split( ''))'。如果不需要漠不关心的访问,那么active_support也是如此。 –

+0

@SergioTulentsev给定标签指定Rails糟透了(像往常一样:) – mudasobwa

+0

啊,也有阵列。在这种情况下,你的答案确实更好。 –

2

鉴于这种访问的边缘情况,我建议创建一个新的类来处理这种情况并将输入均匀化为一致的结构。在这种情况下,Hash(可能更边缘的情况下评论欢迎

class DepthAccessor 
    class AccessFailure < StandardError 
    def initialize(val,current=nil,full=nil,depth=nil) 
     super(
     if full && depth 
      "Failed to find #{val.inspect} for #{current.inspect}:#{current.class} in #{full.inspect} at depth #{depth}" 
     else 
      val 
     end 
    ) 
    end 
    end 

    def initialize(target) 
    raise ArgumentError, "#{target.inspect}:#{target.class} must respond_to :each_with_object" unless target.respond_to?(:each_with_object) 
    @target = homogenize(target) 
    end 

    def path_search(path,sep: '.',raise_on_fail: true) 
    split_path = path.split(sep) 
    split_path.each_with_index.inject(@target) do |acc,(val,idx)| 
     begin 
     acc.is_a?(Hash) ? acc[val] : raise 
     rescue StandardError 
     if raise_on_fail 
      raise AccessFailure.new(val,acc,@target,split_path[0..idx].join(sep)) 
     else 
      nil 
     end 
     end 
    end 
    end 


    private 

    def homogenize(val) 
     case val 
     when Array 
      val.each_with_index.with_object({}) {|(v,idx),obj| obj[idx.to_s] = homogenize(v) } 
     when Hash 
      val.each_with_object({}) { |(k,v),obj| obj[k.to_s] = homogenize(v) } 
     else 
      val 
     end 
    end 
end 

当你创建一个新的实例中的所有键都转换为Strings和所有Array转换为使用indexkey

Hash ES

然后使用这样

target = { 
    :a => { 
    "b" => [ 
     { 
     :x => "here" 
     } 
    ] 
    } 
} 

DepthAccessor.new(target).path_search('a.b.0.x') 
#=> "here" 
DepthAccessor.new(target).path_search('a.b.c.x') 
#=> Failed to find "c" for {"0"=>{"x"=>"here"}}:Hash in {"a"=>{"b"=>{"0"=>{"x"=>"here"}}}} at depth a.b.c 

Full Example

如果Array小号的预订要求那么这里是另一个例子(有一些额外的功能)Example

相关问题