Depth-first traversal, breadth-first traversal to achieve a deep copy of the object

Keywords: PHP

dfs

Depth-First-Search is a kind of search algorithm. It traverses the nodes of the tree along the depth of the tree and searches the branches of the tree as deep as possible. When all edges of node v have been explored, it will be traced back to the starting node on the side where node v was found. This process continues until the source node has been explored to all other nodes. If there are still undiscovered nodes, select one of the undiscovered nodes as the source node and repeat the above operation until all nodes have been explored.
Simply put, DFS traces back from one node in the graph to the last one, and then traces back to the next path until it reaches all the nodes, so it goes back and forth until there is no path.

DFS and BFS are generally used to solve the traversal of graphs, but here, since they are the front-end, I use DFS and BFS to traverse DOM trees.
The following is implemented in the form of stack or recursion:
DOM Node

<div class="parent">
    <div class="c-1">
        <div class="c-1-1">
        </div>
        <div class="c-1-2">
        </div>
        <div class="c-1-3">
        </div>
        <div class="c-1-4">
        </div>
        <div class="c-1-5">
        </div>
    </div>
    <div class="c-2">
        <div class="c-2-1">
        </div>
        <div class="c-2-2">
        </div>
        <div class="c-2-3">
        </div>
        <div class="c-2-4">
            <div class="c-2-4-1">
            </div>
            <div class="c-2-4-2">
            </div>
            <div class="c-2-4-3">
            </div>
            <div class="c-2-4-4">
            </div>
            <div class="c-2-4-5">
            </div>
        </div>
        <div class="c-2-5">
        </div>
    </div>
    <div class="c-3">
    </div>
    <div class="c-4">
    </div>
</div>

DFS Implementation

var node = document.querySelectorAll('.parent')[0];
    //Recursive Writing
    function DFS1 (node, nodeList = []){
        if (node != null){
            nodeList.push(node);
            let children = node.children
            for(let i = 0; i < children.length; i++){
                DFS1(children[i], nodeList)
            }
        }
        return nodeList
    }
    let nodeList = DFS1(node);
    console.log(nodeList);
    //Stack Writing
    function DFS2(node){
        let nodeList = [];
        if (node){
            //Stack Last in First Out
            let stack = [];
            stack.push(node);
            while (stack.length) {
                let _node = stack.pop();
                nodeList.push(_node);
                let children = _node.children;
                //It's written from right to left.
                // for (let i = 0; i < children.length; i++) {
                //     stack.push(children[i]);
                // }
                //right
                for (let i = children.length-1; i >= 0; i--) {
                    stack.push(children[i]);
                }
            }
        }
        return nodeList;
    }
    let nodeList2 = DFS2(node);
    console.log(nodeList2);

As a result, the above DFS1 and DFS2 results are the same.

breadth-first search

Breadth-First-Search starts from the root node and traverses the nodes along the width of the graph. If all the nodes have been visited, the algorithm terminates. BFS is also a blind search. Queue data structure is generally used to assist the implementation of BFS.

Or use the DOM node above. BFS is written as follows.
The code is implemented as a queue.

  var node = document.querySelectorAll('.parent')[0];

    function BFS1(node, nodeList = []) {
        if (!node){
            return;
        }
        //Queue first in first out
        var sequeue = [];
        sequeue.push(node);
        while (sequeue.length){
            var _node = sequeue.shift();
            nodeList.push(_node)
            for(var i = 0; i < _node.children.length; i++){
                sequeue.push(_node.children[i])
            }
        }
        return nodeList
    }
    let nodeList = BFS1(node);
    console.log(nodeList);

give the result as follows

There are two ways to realize object deep cloning.

DFS deep cloning

Two issues should be paid attention to in-depth cloning
1. Ring Data Problem: If an object has a ring object, such as obj.a.b.c==== obj.a, it will cause recursion to go into a dead loop, leading to stack explosion errors.
2. Boundary Processing: There are not only primitive types in objects, but also data types such as functions and Set s. We need to deal with them one by one. The following code only solves the problem of duplication of functions.

let _toString = Object.prototype.toString;
let map = {
    array: 'Array',
    object: 'Object',
    function: 'Function',
    string: 'String',
    null: 'Null',
    undefined: 'Undefined',
    boolean: 'Boolean',
    number: 'Number'
}
function getType(obj){
    return _toString.call(obj).slice(8, -1)
}
function isTypeOf(obj, type){
    return map[type] && map[type] === getType(obj)
}

//Deep Cloning
//dfs
/**
 * 
 * Solve three recursive problems. Ring data problem boundary processing (such as function, Set, etc.)
 */
const DFSdeepClone = function (obj, visitedArr = []){
    let _obj = {};
    if (isTypeOf(obj, 'array') || isTypeOf(obj, 'object')){
        let index = visitedArr.indexOf(obj);
        if (index > -1){
            _obj = visitedArr[index]
        }
        else{
            visitedArr.push(obj)
            for (let key in obj){
                _obj[key] = DFSdeepClone(obj[key], visitedArr)
            }
        }
       
    }
    else if(isTypeOf(obj, 'function')){
        _obj = eval( '(' + obj.toString() + ')')//Processing function
    }
    else{
        _obj = obj;//Processing raw values
    }
    return _obj;
}
let testObj = {
    a: 1,
    b: {
        c: 1,
        d: 2
    },
    circle: null,
    e: function() {
        console.log(1);
    }
}
let cloneTestObj = DFSdeepClone(testObj);
let cloneTestObj2 = testObj;
console.log(cloneTestObj);

console.log('Changes after deep cloning');
cloneTestObj.b = {};//Changes after deep cloning
console.log(cloneTestObj);
console.log(testObj);

cloneTestObj2.b = {}; //Changes to references
console.log('Changes to references');
console.log(cloneTestObj2);
console.log(testObj);

//Ring data
let testCircle = {
    a: 1,
    b: {
        c: 1,
        d: 2,
        circle: null,
    },
    e: function() {
        console.log(1);
    }
}
testCircle.b.circle = testCircle.b;
cloneTestCircle = DFSdeepClone(testCircle);//Not dealing with the ring problem is going to explode the stack into a dead loop.
console.log(cloneTestCircle);

BFS deep cloning

let _toString = Object.prototype.toString;
let map = {
    array: 'Array',
    object: 'Object',
    function: 'Function',
    string: 'String',
    null: 'Null',
    undefined: 'Undefined',
    boolean: 'Boolean',
    number: 'Number'
}
function getType(obj){
    return _toString.call(obj).slice(8, -1)
}
function isTypeOf(obj, type){
    return map[type] && map[type] === getType(obj)
}

//Breadth-first in-depth cloning and queuing
//Use copyObj to build a data structure identical to the original object, and assign processable values (such as original values, functions) to the corresponding nodes after processing.
const BFSdeepClone = function (obj, visitedArr = []){
    let copyObj = {};
    let sequeue = [obj];//Enter the queue
    //At the same time, copyObj joined the queue.
    let copySequeue = [copyObj];
    while(sequeue.length){
        let _obj = sequeue.shift();
        let _copyObj = copySequeue.shift();
        if (isTypeOf(_obj, 'array') || isTypeOf(_obj, 'object')){
            for(item in _obj){
                let val = _obj[item];
                if (isTypeOf(val, 'object')){
                    let index = visitedArr.indexOf(val)
                    if (~index){
                        //It's circular data.
                        _copyObj[item] = visitedArr[index];
                    }
                    else{
                        //New object, give copyObj an empty object with corresponding attributes
                        sequeue.push(val);
                        _copyObj[item] = {};
                        copySequeue.push(_copyObj[item]);
                        visitedArr.push(val);
                    }
                    
                }
                else if (isTypeOf(val, 'array')){
                    sequeue.push(val);
                    _copyObj[item] = [];
                    copySequeue.push(_copyObj[item])
                }
                else if(isTypeOf(val, 'function')){
                    _copyObj[item] = eval( '(' + val.toString() + ')');//Processing function
                }
                else{
                    _copyObj[item] = val;//Processing raw values
                }
            }
        }
        else if(isTypeOf(obj, 'function')){
            _copyObj = eval( '(' + _obj.toString() + ')');//Processing function
        }
        else{
            _copyObj = _obj;//Processing raw values
        }
    }

    return copyObj

}

let testObj = {
    a: 1,
    b: {
        c: 1,
        d: 2
    },
    circle: null,
    e: function() {
        console.log(1);
    }
}
let cloneTestObj = BFSdeepClone(testObj);
let cloneTestObj2 = testObj;
console.log(cloneTestObj);

//Ring data
let testCircle = {
    a: 1,
    b: {
        c: 1,
        d: 2,
        circle: null,
    },
    e: function () {
        console.log(1);
    }
}
testCircle.b.circle = testCircle.b;
cloneTestCircle = BFSdeepClone(testCircle);//Not dealing with the ring problem is going to explode the stack into a dead loop.
console.log(cloneTestCircle);
/** 
 * Print as follows
{ a: 1, b: { c: 1, d: 2 }, circle: null, e: [Function] }
{
    a: 1,
        b: { c: 1, d: 2, circle: { c: 1, d: 2, circle: [Circular] } },
    e: [Function]
}
*/

Posted by A584537 on Mon, 05 Aug 2019 02:52:17 -0700