Given a nested array or hash as the receiver and some object as the argument, what is the best way to return the path to an occurrence of the object if the receiver includes the object, or nil otherwise? I define path as an array of array indices or hash keys that leads to the object. The argument object will never be any of the hash keys, and will never appear more than once. For example, I expect:
[
  :a,
  [:b, :c, {:d => :foo}],
  :e,
]
.path_to(:foo) # => [1, 2, :d]
{
  :a => [3, "foo"],
  :b => 5,
  :c => 2,
}
.path_to(3) # => [:a, 0]
When there is no occurrence, return nil:
[:foo, "hello", 3]
.path_to(:bar) => nil
If no one comes up with a reasonable answer, then I will post my own answer shortly.
Here you are my own recursive solution. I am sure that it could be improved but it is a good start and works exactly as requested.
# 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
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With