2017-12-27 373 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