2014-03-05 21 views
5

Ein verschachteltes Array oder ein Hash als Empfänger und ein Objekt als Argument vorausgesetzt, was ist der beste Weg, um den Pfad zu einem Vorkommen des Objekts zurückzugeben, wenn der Empfänger das Objekt enthält, oder nil? Ich definiere Pfad als ein Array von Array-Indizes oder Hash-Schlüssel, die zu dem Objekt führt. Das Argument-Objekt wird niemals einer der Hash-Schlüssel sein und wird niemals mehr als einmal erscheinen. Zum Beispiel habe ich erwarten:Pfad zu einem eingebetteten Objekt

[ 
    :a, 
    [:b, :c, {:d => :foo}], 
    :e, 
] 
.path_to(:foo) # => [1, 2, :d] 

{ 
    :a => [3, "foo"], 
    :b => 5, 
    :c => 2, 
} 
.path_to(3) # => [:a, 0] 

Wenn kein Auftreten, Rückkehr nil:

[:foo, "hello", 3] 
.path_to(:bar) => nil 

Wenn niemand mit einer angemessenen Antwort kommt, dann werde ich kurz meine eigene Antwort posten.

+0

@ toro2k Ich habe es nicht viel geschrieben. – sawa

+0

Der Rückgabewert * muss * ein Array sein? wäre zum Beispiel nicht ': c' genug? es muss "[: c]" sein? – Agis

+0

@sawa Schöne Frage .. Ich habe Verwirrung. wird es im zweiten Beispiel "[: a, 0]" oder "[: a, 1]" sein? –

Antwort

2

Nichts wie ein bisschen Rekursion.

require 'minitest/autorun' 

class Array 
    def path_to(obj) 
    # optimize this 
    Hash[self.each.with_index.to_a.map {|k,v| [v,k]}].path_to(obj) 
    end 
end 

class Hash 
    def path_to(obj) 
    inverted = self.invert 
    if inverted[obj] 
     [inverted[obj]] 
    else 
     self.map {|k, v| 
     if v.respond_to?(:path_to) 
      if res = v.path_to(obj) 
      [k] + res 
      end 
     end 
     }.find {|path| 
     path and path[-1] != nil 
     } 
    end 
    end 
end 

describe "path_to" do 
    it "should work with really simple arrays" do 
    [:a, :e,].path_to(:a).must_equal [0] 
    end 
    it "should work with simple arrays" do 
    [:a, [:b, :c], :e,].path_to(:c).must_equal [1, 1] 
    end 
    it "should work with arrays" do 
    [:a, [:b, :c, {:d => :foo}], :e,].path_to(:foo).must_equal [1, 2, :d] 
    end 
    it "should work with simple hashes" do 
    {:d => :foo}.path_to(:foo).must_equal [:d] 
    end 
    it "should work with hashes" do 
    ({:a => [3, "foo"], :b => 5, :c => 2,}.path_to(3).must_equal [:a, 0]) 
    end 
end 
+0

Danke für die schnelle Antwort. Ich habe mich gefragt, was ich unter deiner und Rafas Antwort wählen soll. Rafa war ungefähr dreimal schneller. Beide Antworten sind gut. Vielen Dank. – sawa

+2

Seine Lösung ist besser in Bezug auf die Modulverkapselung auch, IIRC Ruby Menschen mögen es nicht mit Kernobjekten zu verwirren, was eine Schande ist. – Reactormonk

+0

Dies ergab einen Fehler für '{: b => [5],: a => [3]}. Path_to (3)'. – sawa

4

Hier sind Sie meine eigene rekursive Lösung. Ich bin sicher, dass es verbessert werden könnte, aber es ist ein guter Anfang und funktioniert genau wie verlangt.

# path.rb 
module Patheable 
    def path_to item_to_find 
    path = [] 
    find_path(self, item_to_find, path) 
    result = path.empty? ? nil : path 
    result.tap { |r| puts r.inspect } # just for testing 
    end 

    private 

    def find_path(current_item, item_to_find, result) 
    if current_item.is_a?(Array) 
     current_item.each_with_index do |value, index| 
     find_path(value, item_to_find, result.push(index)) 
     end 
    elsif current_item.is_a?(Hash) 
     current_item.each do |key, value| 
     find_path(value, item_to_find, result.push(key)) 
     end 
    else 
     result.pop unless current_item == item_to_find 
    end 
    end 
end 

class Array 
    include Patheable 
end 

class Hash 
    include Patheable 
end 

[ 
    :a, 
    [:b, :c, {:d => :foo}], 
    :e, 
].path_to(:foo) # => [1, 2, :d] 

{ 
    :a => [3, "foo"], 
    :b => 5, 
    :c => 2, 
}.path_to(3) # => [:a, 0] 

[:foo, "hello", 3].path_to(:bar) # => nil 
#end path.rb 

# example of use 
$ ruby path.rb 
[1, 2, :d] 
[:a, 0] 
nil 
+1

Das scheint ziemlich schnell zu laufen. Die Verwendung von Modulen ist beeindruckend. – sawa

+0

Vielen Dank @sawa! –

+0

Entschuldigung. Dies ergab ein falsches Ergebnis für '{: b => [5],: a => [3]}. Path_to (3)'. Erwartet wird '[: a, 0]', aber es gibt '[: b,: a, 0]' zurück. – sawa

-1

Dies ist die Antwort, die ich mir ausgedacht habe.

class Object 
    def path_to obj; end 
end 

class Array 
    def path_to obj 
    if i = index(obj) then return [i] end 
    a = nil 
    _, i = to_enum.with_index.find{|e, _| a = e.path_to(obj)} 
    a.unshift(i) if i 
    end 
end 

class Hash 
    def path_to obj 
    if value?(obj) then return [key(obj)] end 
    a = nil 
    kv = find{|_, e| a = e.path_to(obj)} 
    a.unshift(kv.first) if kv 
    end 
end 
Verwandte Themen