0

I have the following recursive function:

 function explore(v, componentN) {
    return function () {
        nodes[v].visited = true;
        nodes[v].componentNumber = componentN;
        preVisit(v);
        adjList[v].forEach((item) => {
            if (!nodes[item].visited){
                ex(item, componentN)
            }
        });
        postVisit(v);
        return nodes;
    }
}
function ex(item, com){
    trampoline(function () {
        return explore(item, com)
    })
}
function trampoline(fn) {
    while(fn && typeof fn === 'function') {
        fn = fn()
    }
}

I want to use setImmediate() in order to avoid stack overflow issues (it should be implemented in this way and not in the iterative approach). However, when I execute this function I get array res only with the first one element, while I get it full of all elements if I don't use setImmediate() and the same situation appears when I use nextTick() (I know in advance how many elements should be there). What am I doing wrong and how can I implement this function (or analog) properly here?

This is explore() function before applying trampoline

function explore(v, componentN) {
    nodes[v].visited = true;
    nodes[v].componentNumber = componentN;
    preVisit(v);
    adjList[v].forEach((item) => {
        if (!nodes[item].visited){
            explore(item, componentN)
        }
    });
    postVisit(v);
    return nodes;
}

It accepts two arguments: v is an index in nodes array which element is supposed to be explored and componentN which is just a counter of components in a graph. explore() does not return anything, it just changes the state of an object in the array nodes under the v key from unexplored to explored. The main problem is that two functions preVisit(v) and postVisit(v) also change the state of that object - write order on which it was visited for the first time and the second time (when popping up stack from the previous calls) respectively. When I converted explore() with trampoline, they began to write unexpected and wrong results.

The function is being called in another function in this way:

for (let i = 0; i < vNum; i++) {
        if (nodes[i].visited) continue;
        explore(i, cN);
        cN++;
    }

And two function postVisit and preVisit

function preVisit(v){
    nodes[v].preVisit = visitCounter;
    visitCounter++;
}
function postVisit(v) {
    nodes[v].postVisit = visitCounter;
    visitCounter++;
}
turisap
  • 231
  • 4
  • 13
  • Possible duplicate of [How do I replace while loops with a functional programming alternative without tail call optimization?](https://stackoverflow.com/questions/43592016/how-do-i-replace-while-loops-with-a-functional-programming-alternative-without-t) – Mulan Oct 16 '17 at 07:49
  • do **not** use `setImmediate` to avoid stack overflows - see [my answer](https://stackoverflow.com/a/43596323/633183) for details – Mulan Oct 16 '17 at 07:51
  • @naomik, thanks for your answer, I've read it and tried to implement trampolines. So it did work and stack overflow issue disappeared but another one appeared. I work with graphs in this problem and I have two function calls in the body of recursive function, namely `write()`. After adding trampolines they write wrong information and I get unexpected results. Could you tell me how can I fix this problem? – turisap Oct 16 '17 at 16:03
  • paste your updated attempt – Mulan Oct 16 '17 at 16:05
  • @naomik, thanks for answering, I just edited my code in the initial question from abstract to real. So I experience problems with `preVisit()` and `postVisit()` functions which write wrong post-order in comparison with that case when I don't use trampolines. I tried to wrap them in `setInterval()` but without any success. – turisap Oct 16 '17 at 16:29
  • now *this* is a fun question ! i won’t have time to get to this until a bit later. ill write back soon if you still need help then ^_^ – Mulan Oct 16 '17 at 16:35
  • @naomik, unfortunately for me this question is not really funny one, at least until I find a solution))) I stuck with this several days ago and it looks like I won't solve it any time soon. I will be looking for your answer!! – turisap Oct 16 '17 at 16:38
  • can you post your code before attempting to convert it with a trampoline ? it would also help to see an example of your input data and what you expect `explore` to do to it (or the expected output, etc) – Mulan Oct 17 '17 at 17:13
  • @naomik, thanks for helping me! I added the previous version of `explore` as well as description of how it's anticipated to work – turisap Oct 17 '17 at 17:54
  • *"The main problem is that two functions preVisit(v) and postVisit(v) also change the state of that object"* – can you post `preVisit`, `postVisit` then too? – Mulan Oct 17 '17 at 18:53
  • side question: it looks like you're recursively updating all adjacent nodes – doesn't that mean that you're *entire* graph is going to get updated ? is that your intention ? or does `preVisit`/`postVisit` modify the nodes in such a way that would prevent traversal/transformation in certain parts of the graph ? – Mulan Oct 17 '17 at 18:55
  • @naomik I added `postVisit` and `preVisit` in my question. No, they don't prevent particular nodes from being explored, they just write `postVisit` and `preVIsit` properties to each node of a graph. And yes, the entire graph is supposed to be updated by these functions. – turisap Oct 18 '17 at 06:06
  • @naomik, is it still possible to get a solution?))) – turisap Oct 22 '17 at 13:01
  • sorry for the delay; added some help below. – Mulan Oct 28 '17 at 00:07

1 Answers1

0

Let's say we have an ordinary recursive function like this – the problem here is we have a forEach loop where each call to explore can yield 0, 1, or more additional calls to explore – To fix this, we'll need some way to sequence all of the nodes such that we can process them one-by-one without blowing up the stack

const explore = node =>
  {
    node.visited = true
    preVisit (node)
    Array.from (node.adjacencies)
      .filter (n => n.visited === false)
      .forEach (n => explore (n))
    postVisit (node)
  }

We introduce a main loop inside the explore function which processes an array of nodes, instead of just a single node. The loop will continue to run while there are nodes in the array. Recursive calls will be made to the inner loop function instead of the outer explore function. This array approach works nicely because each node has an ambiguous amount of adjacencies – when can easily add 0, 1, or more adjacent nodes to this list – also note the continuation k is added so we have a way to sequence the postVisit calls in the correct order

Of most primary importance tho is the recursive call to loop is in tail position – which prepares us for the next transformation

const explore = node =>
  {
    const loop = ([x, ...xs], k) =>
      {
        if (x === undefined)
          k ()
        else {
          x.visited = true
          preVisit (x)
          loop (
            Array.from (x.adjacencies)
              .filter (n => n.visited === false)
              .concat (xs),
            () => (postVisit (x), k ())
          )
        }
      }
    return loop ([node], x => x)
  }

Once we have a tail-recursive loop, we can introduce loop/recur for stack-safe recursion

const recur = (...values) =>
  ({ type: recur, values })

const loop = f =>
  {
    let acc = f ()
    while (acc && acc.type === recur)
      acc = f (...acc.values)
    return acc
  }

const explore = $node =>
  loop (([x,...xs] = [$node], k = x => x) => {
    if (x === undefined)
      return k ()
    else {
      x.visited = true
      preVisit (x)
      return recur (
        Array.from (x.adjacencies)
          .filter (n => n.visited === false)
          .concat (xs),
        () => (postVisit (x), k ())
      )
    }
  })

// for completion's sake
let visitCounter = 0

const preVisit = node =>
  node.preVisit = visitCounter++

const postVisit = node =>
  node.postVisit = visitCounter++
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • @Kirill, sorry for the delay, it's been a really busy month for me. I added a write-up here, but it's very uncharacteristic of me to make an answer without a including functioning code demo. In the near future, I will try to attach one to this post to show that this works with millions of nodes and adjacencies without an issue. In the meantime, try to understand the first transformation step where we introduce the array and the continuation. – Mulan Oct 27 '17 at 23:58
  • first of all, thank you for this explanatory answer! I'm trying to wrap my hand now about what is happening in the code but now it feels pretty complicated) I'll definitely will try to get it but I guess it will take several attempts) If you find time to attach a demo to this code that would be pretty cool, but, I think I will get it anyway) Thanks for your help! – turisap Oct 30 '17 at 11:41