1

I have a list of addresses that we can call A-Z. I have used the Google Maps api to geocode these and then find the travel times between each of them to get a 26x26 matrix. I want to come up with a function that takes the address list, the matrix, target node ( which I will call @), and a time limit and returns a list of all possible travel paths that can be achieved from A to @ to any number of different, non-repeating destinations, and back to A before the time limit is reached.

I am thinking that a recursive function might be the best way to do this but I have only learned about them in theory and don't know if it would work.

My thoughts so far are something like this:

var duration = 0;
var path = [];

function getPath(addList, matrix, targetNode, currentNode, timeLimit){
    if(duration+matrix[matrix.indexOf(currentNode), matrix.indexOf(A)]>=timeLimit{
        path[path.length-1] = A;
        // (1) add path to the list of possible paths
    }
    else{
        var tempList = addList;
        tempList.splice(tempList.indexOf(currentNode), 1);
        for(int i = 0; i < tempList.length; i++){
            // (2) increase duration by matrix[matrix.indexOf(currentNode), matrix.indexOf(tempList[i])];
            getPath(tempList, matrix, targetNode, tempList[i], timeLimit);
        }
    }
}

I think that it would work if I could figure out:

(1) How should I store the path (A,@,...,A) as I go into the recursiveness? I cant use a single variable because each function would change it. I was thinking about using something like an xml file as a tree but that seems over the top.

(2) In a similar question, how would I continue increasing duration for each possible path?

On a final note, this would only give me the longest paths from A to @ to other nodes and back to A. I would want to include shorter paths as options as well. For example if my path is {A, @, B, C, D, A} then I would also want to include the following paths {A, @, A}, {A, @, B, A}, and {A, @, B, C, A}. The recursiveness would stop early if I stopped there though so I think that should be handled as a follow up for once the getPath function is totally resolved.

Any pointers, thoughts, help, or comments would be greatly appreciated! Thanks in advance!

Update: I just thought of using a JSON object to handle my issues. It could look something like:

var jsonPaths = [
    {
        index: 1,
        path: [A,@,A],
        duration: 187
    },
    { .... },
    { .... }
];

and I could push to it and reference it on each function call.

Timo Loescher
  • 119
  • 1
  • 1
  • 16

1 Answers1

1

You can do this by creating a array of all paths to the target with a distance less than the maximum allowable distance minus distance of the shortest path to the target.

I would do this in two steps:

First, go through and compute the shortest path from every node to the target. Save each shortest path, along with its distance, on the node.

With all of the shortest paths computed, you can then start creating a array of possible partial paths, and a array of possible paths. To be a possible partial path, the total distance of the path plus the distance of the shortest path from the last node in the path to the target is less than the maximum path length. Possible paths are paths from a to @ that are less than the maximum path length.

You can iterate through your array of possible partial paths, at each step removing the first possible partial path and for all nodes you can travel to from the current node, check if adding that node to your current path would be a possible partial path. If so, add it to your possible partial path array . 'Nodes you can travel to' would mean nodes that you haven't already visited that are connected to your current node.

Right now, it looks like you have all the nodes connected to all of the other nodes. This could be problematic, since it gives you a maximum of 4.0329146e+26 (a.k.a 26!) possible paths from a to @. Generally, for these types of problems, you limit nodes to only be connectable to nearby nodes to avoid that much of a scaling problem, but you will still potentially be generating huge numbers of paths. ALL possible paths is potentially a large number.

I would recommend against recursion since you're generating a list of lots of things. Just create an array of possibilities and keep removing things from it/adding things to it until it's empty.

EDIT: Since all of your nodes are connected to all of the other node, from any possible potential path, you can create a path by adding the target node. This simplifies things as far as finding paths is concerned.

To find possible paths, consider that any possible path is another possible path plus one node. If you know that ABC is a possible path, you can check ABCD, ABCE, ABCF, and so on, as well as adding ABC@ to an array of half-paths.

Likewise, if ABD is not a possible path, you don't need to consider anything else starting with that path. To come up with all paths from A to @, you can start with the array [A].

Your function would look something like this:

var partialPaths = [A],
    nodes = [/*this contains all of your nodes*/],
    paths = [],
    maxDistance = totalDistance - distance([A@]), 
      //max distance of any path between A and @
    currentPath;

while(partialPaths.length > 0){
    currentPath = partialPaths.pop();

    paths.push(currentPath.concat([@]); //Adds the current path plus @

    nodes.forEach(function(node){
        if(distance(currentPath + node + @) < maxDistance &&
          (node not in current path){
            partialPaths.push(currentPath + node);
        }
    });
}

After this, you will have an array of all paths from A to @. Simply combine each path with all paths of length less that the total length minus the maximum length to get all of the round-trip paths.

ckersch
  • 7,507
  • 2
  • 37
  • 44
  • ''First, go through and compute the shortest path from every node to the target. Save each shortest path, along with its distance, on the node.'' Wouldn't the shortest path from everynode to the target be the direct path? That is included in the matrix variable. Also, while I agree that in theory there would be 26! combinations, in practicality for the application that I am building no path is going to be much longer than 6 nodes and definitely no longer than 8 due to inter-node travel time and the maximum travel time. Either way, can you elaborate on the last point? Thanks! – Timo Loescher Aug 20 '14 at 18:29
  • I'd forgotten that everything is connected to everything else, so yes, the shortest path on everything is directly to the target, which simplifies things quite a bit. The danger with recursion is that you end up creating an instance of your function for each layer of you go depth-first, and an instance for each path if you go breadth-first. There isn't a compelling reason to store all of those since you want to save all of the results at the end. – ckersch Aug 20 '14 at 19:28
  • So then what is a better way of doing it? I couldn't figure out how to loop through all the options and keep track of where I was in combinations. Eventually, every node will have a weight and there will be a capacity constraint included but I am doing the simplest method which unfortunately means doing the most computations. With only 26 options, it isn't too unreasonable for me to generate the entire set. Computational time isn't too concerning as the process that this application will be replacing takes about an hour or more of human labor a day. – Timo Loescher Aug 20 '14 at 23:01
  • You can store all of the possible paths in a list. That way, you are storing all of the paths you want to investigate without keeping active function calls in memory, which you would be doing with recursion. – ckersch Aug 21 '14 at 14:04
  • How would I generate the possible list of paths? Just generate the entire set and whittle down from there? That would mean I hit every combo which would be as you said 26! paths. My idea with the recusrsion was to catch paths at the cut off point to not explore more options with the same stem and therefore vastly reduce the number of paths searched. I don't know how best to approach your method, probably because I don't know enough about CS to get why it uses less memory. Thanks so much for your help by the way! – Timo Loescher Aug 21 '14 at 21:28
  • What do you think about my idea in the update above? – Timo Loescher Aug 21 '14 at 21:42
  • That roughly what I was thinking, though that's an array, not a JSON object. (Which is fine, because JSON is usually just for passing stuff from a server to a client or vice versa.) – ckersch Aug 21 '14 at 21:48
  • I added some example pseudocode that should (hopefully) make more sense. – ckersch Aug 21 '14 at 22:08
  • Ok I had to read through it a few times and look up the pop function but I understand what you are doing and it is very clean and well done. My only questions are: in this line "if(distance(currentPath + node + @)" should @ be A? Cuz you already added @ in the path? Also, to clarify, in the second half (not pseudocoded), it cant be any combo, has to be one without repeat nodes from the partial path – Timo Loescher Aug 22 '14 at 04:02
  • No. You've added *another* path with `@` in it to your list of paths, but the current path does not have `@` in it. `A` is the start node, so it's already in the path. You only add `@` when you create a complete path from `A` to `@` from a partial path. – ckersch Aug 22 '14 at 14:22
  • And in that case, you can use the `.filter()` method on the array to remove all return paths that have nodes in common with your path from `A` to `@`. – ckersch Aug 22 '14 at 14:53