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