Прохождение дерева DOM

Недавно я дал интервью для должности инженера-фронтмена в Facebook. Для моего экрана телефона у меня был следующий вопрос: учитывая, что node из дерева DOM найдите node в той же позиции из идентичного дерева DOM. Для ясности см. Диаграмму ниже.

 A         B

 O        O
 |\       |\
 O O      O O
  /|\      /|\
 O O O    O O O
      \        \
       O        O

Вот мое решение, мне было интересно, что я мог бы сделать, чтобы улучшить/оптимизировать его.

var rootA, rootB;

function findNodeB(nodeA) {
    // Variable to store path up the DOM tree
    var travelPath = [];

    // Method to travel up the DOM tree and store path to exact node
    var establishPath = function(travelNode) {
        // If we have reached the top level node we want to return
        // otherwise we travel up another level on the tree
        if (travelNode === rootA) {
            return;
        } else {
            establishPath(travelNode.parentNode);
        }

        // We store the index of current child in our path
        var index = travelNode.parentNode.childNodes.indexOf(travelNode);
        travelPath.push(index);     
    }

    var traverseTree = function(bTreeNode, path) {
        if(path.length === 0) {
            return bTreeNode;
        } else {
            traverseTree(bTreeNode.childNodes[path.pop()], path);
        }
    }

    establishPath(rootB, nodeA);

    return traverseTree(rootB, travelPath);
}           

Ответ 1

Так как по крайней мере Аксель проявил интерес к итеративному решению, то здесь:

Для двух деревьев с одинаковой структурой и указанного node в первом дереве найдите node во втором дереве с тем же положением внутри структуры.

Если у нас нет другой информации о двух деревьях, то положение каждого node можно охарактеризовать как путь от корня node, где каждый шаг в пути указывается как индекс в массив childNode.

function indexOf(arrLike, target) {
    return Array.prototype.indexOf.call(arrLike, target);
}

// Given a node and a tree, extract the nodes path 
function getPath(root, target) {
    var current = target;
    var path = [];
    while(current !== root) {
        path.unshift(indexOf(current.parentNode.childNodes, current));
        current = current.parentNode;
    }
    return path;
}

// Given a tree and a path, let locate a node
function locateNodeFromPath(root, path) {
    var current = root;
    for(var i = 0, len = path.length; i < len; i++) {
        current = current.childNodes[path[i]];
    }
    return current;
}

function getDoppleganger(rootA, rootB, target) {
    return locateNodeFromPath(rootB, getPath(rootA, target));
}

РЕДАКТИРОВАТЬ: Как отмечено Blue Skies, у childNodes нет .indexOf(). Обновление с помощью Array.prototype.indexOf.call()

Ответ 2

Я бы пересекал два дерева параллельно, и когда я добрался до node в treeA, верните параллельный node.

Ответ 3

Вместо Array.prototype.indexOf.call вы можете использовать Array.from (стандартизованный в ES6):

Array.from(travelNode.parentNode.childNodes).indexOf(travelNode);

Ответ 4

Здесь находится решение для параллельного перемещения

function findDomNodeInTree(rootA, rootB, targetNode) {
    if (rootA === targetNode){
        return rootB;
    }

    var nodeB = null;

    for (let i=0; i<rootA.childNodes.length && nodeB === null; i++){
        nodeB = findDomNodeInTree(rootA.childNodes[i], rootB.childNodes[i], targetNode);
    }

    return nodeB;
}

Это временная сложность O (N), и в худшем случае нам нужно пересечь целые деревья.

Я не думаю, что это менее эффективно, чем решение с поиском пути в первую очередь. На каждом уровне есть вызов indexOf, которому может понадобиться пройти все узлы этого уровня, чтобы найти индекс.