简体   繁体   中英

Ruby - finding all paths through a graph from a given starting point

The hash map is populated as such:

{"1"=>["2"], "2"=>["3", "7"], "3"=>["4"], "5"=>["6", "2"], "6"=>["7"], "7"=>["8", "4"]}

so that each key can have multiple values. These values represent bus stops from a set of routes eg from bus stop 1 you can get to 2, from bus stop 2 you can get to 3 or 7 and so on.

I am trying to traverse this graph structure and find all possible paths of length greater than 1 from a given starting point. For example, for starting point of 1, the list of all possible paths would be

[[1,2] , [1,2,3] , [1,2,3,4] , [1,2,7] , [1,2,7,8], [1,2,7,4]]

I am trying to solve this problem recursively, where I iterate over the children of the current key (the values of that key) and call a function which essentially expands the children of that key. Here is my code so far:

if hash.has_key?(origin.to_s)
  tmp = origin.to_s
  tmp << hash[origin.to_s][0]
  paths.push(tmp)
  expand_node(hash,paths,paths.length-1,tmp[tmp.length-1])
end

Origin is the starting point. To start, I add the first path (in this case [1,2] ) to an array called paths , then I expand the last value added to the previous path (in this case 2 ).

def expand_node(hash,paths,curr_paths_index,node)
curr_node = node
if hash.has_key?(node.to_s)
  counter = 0
  hash[curr_node].each do |child|
    tmp = paths[curr_paths_index]
    tmp << child
    paths << tmp
    curr_paths_index += counter + 1
    puts paths
    expand_node(hash,paths,curr_paths_index,child)
  end
end

end

The argument curr_paths_index keeps track of the path from which I expand. The argument path is the whole list of paths currently found. The argument node is the current value being expanded.

Printing paths after the function finishes yields:

123 123 1234 1234 1234 12347 12347 12347 12347 123478 123478 123478 123478 123478 1234784 1234784 1234784 1234784 1234784 1234784

Is there any way to modify this code so that it produces the desired output (shown above)? Is there a better way of solving this problem in general?

Thanks.

One way to solve a recursive problem is to first break it down into a single case that is easy to solve and then generalize that one case. Here is an easier case:

Given a graph and path through that graph, determine which nodes can be added to the end of that path without creating a loop.

If we can solve this problem we can easily solve the larger problem as well.

  1. Start with a path that is just your starting node
  2. Find all nodes that can be added to that path without creating a loop
  3. For each such node, output a path which is your initial path plus that node and...
  4. Recursively repeat from step 2, using the new path

Note that if no new nodes are found in step 2, the recursive call will terminate because steps 3 and 4 have nothing to do.

Here is how I would solve step 2, I'll leave the recursive part to you

def find_next_nodes graph, path
  graph[path[-1]].reject { |node| path.include? node }
end

I would do it like this, pretty sure this can be optimized further and there maybe an issue with performance too.

require 'set'

dt = {"1"=>["2"], "2"=>["3", "7"], "3"=>["4"], "5"=>["6", "2"], "6"=>["7"], "7"=>["8", "4"]}

def traverse(initial_hash, key_arrays, traversed_keys = [])
  value = []
  key_arrays.map do |key|
    n = [*traversed_keys, key]
    value << n unless n.count == 1 # prevent first key to be added
    another_keys = initial_hash.fetch(key, nil) # try to load the value, fallback to nil if key not found
    if another_keys.nil? # stop condition
      value << n
    elsif another_keys.is_a?(Enumerable) # if key found
      another_keys.map do |ank| # recursive
        value.concat([*traverse(initial_hash, [ank], n)]) # splat operator to unpack the array
      end
    end
  end
  value.to_set # only return unique value, can be converted to array if needed
end

traverse(dt, [1].map(&:to_s)).map { |val| val.join('') }

Sorry, but I have not debugged your code, so I think there's an issue with the temp variable there since the path which can't be expanded anymore is carried over to next iteration.

def doit(graph, start)
  return [] unless graph.key?(start)
  recurse(graph, start).drop(1)
end

def recurse(graph, start)
  (graph[start] || []).each_with_object([[start]]) { |next_node, arr|
    recurse(graph, next_node).each { |a| arr << [start, *a] } }
end

graph = { 1=>[2], 2=>[3, 7], 3=>[4], 5=>[6, 2], 6=>[7], 7=>[8, 4] }

doit(graph, 1)
  #=> [[1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 7], [1, 2, 7, 8],
  #    [1, 2, 7, 4]] 
doit(graph, 2)
  #=> [[2, 3], [2, 3, 4], [2, 7], [2, 7, 8], [2, 7, 4]] 
doit(graph, 3)
  #=> [[3, 4]] 
doit(graph, 5)
  #=> [[5, 6], [5, 6, 7], [5, 6, 7, 8], [5, 6, 7, 4], [5, 2],
  #    [5, 2, 3], [5, 2, 3, 4], [5, 2, 7], [5, 2, 7, 8], [5, 2, 7, 4]] 
doit(graph, 6)
  #=> [[6, 7], [6, 7, 8], [6, 7, 4]] 
doit(graph, 7)
  #=> [[7, 8], [7, 4]]     
doit(graph, 4)
  #=> [] 
doit(graph, 99)
  #=> [] 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM