Рекурсия с помощью setImmediate()

У меня есть следующая рекурсивная функция:

 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()
    }
}

Я хочу использовать setImmediate() во избежание проблем переполнения стека (это должно быть реализовано таким образом, а не в итеративном подходе). Однако, когда я выполняю эту функцию, я получаю массив res только с первым элементом, в то время как я заполняю его всеми элементами, если не использую setImmediate() и такая же ситуация возникает, когда я использую nextTick() (Я заранее знаю, сколько там должно быть элементов). Что я делаю не так и как я могу правильно реализовать эту функцию (или аналог) здесь?

Это explore() функционировать перед применением батута

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;
}

Он принимает два аргумента: v это индекс в nodes массив, какой элемент должен быть исследован и componentN который просто счетчик компонентов в графе. explore() ничего не возвращает, просто меняет состояние объекта в массиве nodes под v ключ от неизведанного к исследованному. Основная проблема в том, что две функции preVisit(v) а также postVisit(v) также измените состояние этого объекта - порядок записи, в котором он был посещен в первый и второй раз (при отображении стека из предыдущих вызовов) соответственно. Когда я обратился explore() с батута они начали писать неожиданные и неправильные результаты.

Функция вызывается в другой функции следующим образом:

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

И две функции postVisit а также preVisit

function preVisit(v){
    nodes[v].preVisit = visitCounter;
    visitCounter++;
}
function postVisit(v) {
    nodes[v].postVisit = visitCounter;
    visitCounter++;
}

1 ответ

Решение

Допустим, у нас есть обычная рекурсивная функция, подобная этой, - проблема в том, что мы имеем forEach цикл, где каждый вызов explore может выдать 0, 1 или более дополнительных звонков explore - Чтобы это исправить, нам понадобится какой-то способ упорядочить все узлы так, чтобы мы могли обрабатывать их один за другим, не разрушая стек

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

Мы вводим основной цикл внутри explore функция, которая обрабатывает массив узлов, а не только один узел. Цикл будет продолжать работать, пока в массиве есть узлы. Рекурсивные вызовы будут выполняться для функции внутреннего цикла вместо внешнего explore функция. Этот подход с массивами работает хорошо, потому что каждый узел имеет неоднозначное количество смежностей - когда можно легко добавить 0, 1 или более смежных узлов в этот список - также обратите внимание на продолжение k добавлено, поэтому у нас есть способ упорядочить вызовы postVisit в правильном порядке

Наиболее важным является рекурсивный вызов loop находится в хвостовой позиции - что готовит нас к следующей трансформации

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)
  }

Когда у нас есть хвост-рекурсивный цикл, мы можем ввести loop/recur для безопасной рекурсии

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++
Другие вопросы по тегам