0

I have an object: {1:[2,3], 2:[3,3], 3:[4,8], 4:[8,8], 8:["End", "End"]} which I would like to loop/traverse through.

  • Each key of this object is like a point or location (eg location 1 or location 2).
  • And each location is linked to one or two other locations, allowing you to jump from one to the next. These "links" are defined by the array-property of each key. For example, I can jump from location 1 to location 2, or from location 3 to location 8.

Now I would like to map out these links in the form of a 2D array. The 2D array should contain all possible paths, with each path starting with location 1 and ending with location End (defined only in the array of key 8).


I have the following code so far:

let paths_list;
function find_paths(obk, data={}) {
    paths_list = data?.paths_list || [];
    let newest_path = data?.newest_path || [1];
    let current_number = data?.current_number || 1;
    let next_possible_numbers_iteration = (data.next_possible_numbers_iteration || 0);

    if((current_number == "End" || current_number == undefined) && next_possible_numbers_iteration >= obk[1].length) {
        return paths_list;
    }

    let next_possible_numbers;
    let next_number;

    for(let i = 0; i < 2; i++) {
        if(current_number == "End" || current_number == undefined) {
            next_possible_numbers_iteration++;
            current_number = 1;
            paths_list.push(newest_path);
            newest_path = [1];
        }
        next_possible_numbers = obk[current_number]; // [2,2]
        next_number = next_possible_numbers[next_possible_numbers_iteration];
        newest_path.push(next_number);
        current_number = next_number;
    }
    

    if(current_number != "End" && current_number != undefined) {
        find_paths(obk, {
            paths_list,
            newest_path,
            current_number,
            next_possible_numbers_iteration
        });
    } else {
        next_possible_numbers_iteration++;
        paths_list.push(newest_path);
        find_paths(obk, {
            paths_list,
            current_number,
            next_possible_numbers_iteration
        });
    }
}


let my_obk = {1:[2,3], 2:[3,3], 3:[4,8], 4:[8,8], 8:["End", "End"]};
find_paths(my_obk);
console.log(paths_list);

Problem

But there is a slight problem: The function returns only some of the possible paths. Specifically, it returns these correctly:

  • 1,2,3,4,8,End
  • 1,3,8,End

But does not return other paths such as:

  • 1,2,3,8,End

This is because the function only searches in the same position in each array-property and does not fluctutate the position at which it searches (defiend by next_possible_numbers_iteration).

In short, my question is how would I make the func look at different positions in each array (or property) instead of just one in all arrays. This probably does not make sense when you read it, but if you look at how the variable next_possible_numbers_iteration is used, you'll see what I mean. Feel free to change my function if it is hopeless.

Krokodil
  • 1,165
  • 5
  • 19
  • Seems similar to [Get possible question answer paths (JavaScript)](https://stackoverflow.com/q/68037804) which I recently answered. There are also two other excellent answers to that questions. You can look for inspiration. – VLAZ Jun 22 '21 at 14:29
  • Thank you ill take a look – Krokodil Jun 22 '21 at 14:33
  • 1
    [Here is my approach from that question](https://jsbin.com/beqironaxa/1/edit?js,console) adapted for your dataset. [This is the solution from Nur](https://jsbin.com/qogalibomo/1/edit?js,console). And [this is the solution from Thank you](https://jsbin.com/ruwepewixo/1/edit?js,console). (had to fiddle a bit with the printing, otherwise JSBin was throwing errors. Something to do with the loop protection it applies, I think. That's why the end may be a bit different for the last two). – VLAZ Jun 22 '21 at 14:55
  • What should happen when there is twice the same link for the same node? Should that result in separate paths? Or is the result for `2: [3,3]` exactly the same as if it was just `2: [3]`? If not, then how will you know which is which in the output? – trincot Jun 22 '21 at 14:58
  • @trincot yes, the result for `2: [3,3]` should be the same as `2: [3]`, I just wrote it like that to make it uniform – Krokodil Jun 22 '21 at 15:04
  • 1
    Damn both answers are great and work well. I ran speed tests and they are pretty much the same. – Krokodil Jun 22 '21 at 16:15

2 Answers2

1

You could take a recursive approach and collect all pathes from the given node.

This approach takes only unique targets the in the arrays.


A recursive function contains usually an exit condition where the function decides to call the function again or just leave the function with a value. This happens here, too.

If a node is found which is the target, it returns a nested array with a single value, the target node.

To understand the algorithm, take a simple three node object, like

{ 1: [2, 3], 2: ['End'], 3: ['End'] }

where the final result would be two pathes:

[
    [1, 2, 'End'],
    [1, 3, 'End']
]

The algorithm works with a breadth-first search, because of the recursion and finding the end.

That means it returns for the final call of getPathes('End', 'End')

[['End']]

and for the previous call of getPathes(2, 'End') it returns

[[2, 'End']]

analog for getPathes(3, 'End') it returns

[[3, 'End']]

Both arrays are returned inside of the call of getPathes(1, 'End'), where the actual node is prepended to all arrays of the nested call.

const 
    getPathes = (from, to) => {
        if (from === to) return [[to]];
        return nodes[from].flatMap(n => getPathes(n, to).map(p => [from, ...p]));
    },
    nodes = { 1: [2, 3], 2: [3], 3: [4, 8], 4: [8], 8: ["End"] },
    pathes = getPathes(1, 'End');

pathes.forEach(p => console.log(...p));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Wow that's amazing and extremely compact. How does the logic work though? I don't understand it as I am a beginner. – Krokodil Jun 22 '21 at 14:33
1

The recursive solution by @Nina looks great

If you want a iterative way of solving the same you can check this

Keep on creating new paths until all paths end : )

const obj = { 1: [2, 3], 2: [3, 3], 3: [4, 8], 4: [8, 8], 8: ['End', 'End'] };

let paths = obj[1].map((x) => [1, x]);
let complete = false;

while (!complete) {
  complete = true;
  
  const result = [];
  paths.forEach((path) => {
    const lastElementOfPath = path[path.length - 1];
    const nextPathDirections = obj[lastElementOfPath];
    if (nextPathDirections) {
      Array.from(new Set(nextPathDirections)).forEach((nextPathElement) => {
        result.push([...path, nextPathElement]);
        if (nextPathElement !== 'End' && !!obj[nextPathElement]) {
          complete = false;
        }
      });
    } else {
      result.push([...path]);
    }
  });
  
  paths = result;
}


// Paths will be your desired result
paths.forEach(path => console.log(...path))
Akhil
  • 879
  • 5
  • 11
  • Love this answer, a combination of simplicity understandability and how clever the idea behind it is! "keep on creating new paths until all possibilities end" Amazing! – Krokodil Jun 22 '21 at 17:41