嵌入对象的路径

时间:2014-03-05 18:54:24

标签: ruby arrays hash

给定嵌套数组或散列作为接收器并将某个对象作为参数,如果接收器包含对象,则返回路径到对象出现的最佳方法是什么,否则为nil?我将 path 定义为数组索引或散列键的数组,这些数组通向对象。参数对象永远不会是任何哈希键,并且永远不会出现多次。例如,我希望:

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

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

如果没有发生,请返回nil

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

如果没有人提出合理的答案,我会很快发布自己的答案。

3 个答案:

答案 0 :(得分:4)

在这里,您是我自己的递归解决方案。我确信它可以改进,但它是一个良好的开端,完全按照要求工作。

# 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 :(得分:2)

没有什么比一点递归。

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

答案 2 :(得分:-1)

这是我想出的答案。

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