Implementation of one-way linked list of javaScript data structure and algorithm

Posted by DamienRoche on Mon, 07 Feb 2022 11:37:27 +0100

introduction

Recently, I am learning the data structure of javaScript. Linked list and array in javaScript can play a complementary relationship. Some complex data structures are suitable for array implementation (such as stack), while others are more suitable for linked list implementation (such as queue). Today, I will study the implementation of one-way linked list in javaScript

What you want to achieve

One way linked list creation, initialization, basic addition, deletion, modification and query. At the same time, because the project I work on uses ts, I hope to implement two versions of javaScript and typeScript. Considering the compatibility problem, my code does not use the class syntax of es6, but uses function to construct class objects, while the TS version directly uses the class syntax of es6

Linked list principle

I don't think I'm too professional. Many articles have also mentioned the principle of linked list. Here I'll just say it briefly. Compared with js arrays, linked lists are fundamentally different in storage mode. Arrays open up a piece of continuous memory in the heap, that is, during the operation of new Array or []. The linked list is a discrete memory. Each stored value exists in the form of a node. Linking these discrete nodes together by pointing to the address of the next node is the so-called linked list.

node

To realize the linked list, we first need to have the node of the linked list. The node of the one-way linked list contains two parts, the node value and the address pointing to the next node, which can also be called a pointer. However, js does not need us to operate the pointer. I won't say more here. Here is the code to realize the node. One more word here is object Create (null) can create an object without a prototype chain. It doesn't matter if it's not used here. I just think node won't use the methods on the object prototype, so it's created like this

// Unidirectional linked list node
function SingleNode(value) {
    let node = Object.create(null);
    node.value = value;  // Node value
    node.next = null;  // Next node
    return node;
}

Linked list creation

In fact, we only need to remember the last node in the one-way linked list, which can also be regarded as the head of the current linked list. I also recorded the length of the linked list here, and added the length method on the SingleLinkList prototype, which can return the length of the linked list when used externally

function SingleLinkList() {
    this.head = null; // Head node
    this.len = 0; // Linked list length
    /**
     * Get the length of the current linked list
     * @returns length
     */
    SingleLinkList.prototype.length = function () {
        return this.len;
    }
}

The next step is to add various methods to the linked list. First, complete the initialization method
Note that the one-way linked list only has one head and the node follows the principle of last in first out, so it only supports the operation of the head of the linked list (equivalent to the operation of the tail of the array, such as push and pop, but does not support the operation of shift and unshift. I plan to implement these methods in the two-way linked list)

Initialization method (construction method)

Here, we hope to initialize the linked list when new SingleLinkList(), that is, the construction method, like let array = new Array(1)
At present, I have implemented initialization in three cases: no parameter, one parameter and multiple parameters. The parameters here support array, string, number, object and other types
Use arguments to judge the number of parameters and process them separately
I added two methods push and insert to the SingleLinkList. In order to distinguish the methods hung on the SingleLinkList prototype later, they are called intPush and InitInsert. One is to create and add nodes to the head, and the other is to distinguish arrays from other types and add them in a circular way. The code is as follows:

// Add method in initialization method
function initPush(param) {
    let node = new SingleNode(param);
    if (this.head) {
        node.next = this.head;
    }
    this.head = node;
    this.len++;
}
// Adding method of construction method
function initInsert(param) {
    if (Object.prototype.toString.call(param) === "[object Array]") {
        for (let i = 0; i < param.length; i++) {
            initPush.call(this, param[i]);
        }
    } else {
        initPush.call(this, param);
    }
}

The self executing init method is then added

// initialization
(function init(params) {
    switch (params.length) {
        case 0:
            break;
        // Node array or single node
        case 1:
            initInsert.call(this, params);
            break;
        // Variable length parameter, multiple nodes
        default:
            for (let i = 0; i < params.length; i++) {
                initInsert.call(this, params[i]);
            }
            break;
    }
}.bind(this, arguments))();

In this way, our one-way linked list has the ability of initialization, and can support the following methods:
new SingleLinkList(1);
new SingleLinkList(1, 2, 3);
new SingleLinkList([1, 2, 3]);
new SingleLinkList(1, 2, [3, 4, 5]);

push and pop

First, add two basic methods, push and pop, which are both very simple. The use method is also similar to the array. It is to add a node at the head of the linked list and return the current head node. The push method needs to judge whether the lower head is empty. If it is empty, it will be added directly to the head. If it is not empty, it will add node Next points to the current head, and then points the head to the newly created node. In addition, because the length is added, the change of length also needs to be handled
Upper code

/**
 * Add a node to the linked list and put it in the head
 * @param {*} value Node value
 */
SingleLinkList.prototype.push = function (value) {
    let node = new SingleNode(value);
    if (this.head) {
        node.next = this.head;
    }
    this.head = node;
    this.len++;
}
/**
 * Pop up the last added node. If the linked list is empty, null will be returned
 * @returns Node value
 */
SingleLinkList.prototype.pop = function () {
    if (this.head) {
        let node = this.head;
        this.head = node.next;
        this.len--;
        return node.value;
    }
    else {
        return null;
    }
}

Query and traversal

Complete the traversal first. The linked list traversal only needs to continuously obtain the next node of the head node, and traverse the linked list again until the next is null. You can use the while loop to solve it. The implementation code is as follows

/**
 * ergodic
 * @param {*} fn Callback function
 */
SingleLinkList.prototype.finds = function (fn) {
    if (fn && Object.prototype.toString.call(fn) === "[object Function]") {
        let node = this.head;
        while (node) {
            fn(node.value);
            node = node.next;
        }
    } else {
        throw new Error("no callback function");
    }
}

Note that because the last in first out principle is followed, the traversal order is not the order you add, but on the contrary. Simply speaking, when traversing the linked list, the return order of new SingleLinkList(1, 2, 3) is 3, 2, 1, rather than 1, 2, 3 like the array for loop

The principle of query is similar. The only difference is to judge whether the value under each while loop is what we need. Therefore, the query method needs to pass in the value we want to query and return the queried value. The all parameter is added here, which is a Boolean value. When it is false by default, only the first query value is returned. Sometimes, we may want to wait until the query value appears in the linked list several times. Therefore, when all is true, we will query until the next is empty, and return an array with all the queried values

/**
 * query
 * @param {*} value Value to query
 * @param {*} all Whether to check all. false only queries the first one. true checks all and returns the value array
 * @returns Node value or node value array, no null value or empty array returned
 */
SingleLinkList.prototype.find = function (value, all = false) {
    let node = this.head;
    let nodeArr = [];
    while (node) {
        if (node.value === value) {
            if (all) {
                nodeArr.push(node.value);
            } else {
                return node.value;
            }
        }
        node = node.next;
    }
    if (all) {
        return nodeArr;
    } else {
        return null;
    }
}

In this way, the traversal and query are completed. Is it over? Of course not. Only some basic types of queries can be supported here, such as numbers, strings, Boolean values, etc. However, if the value of the node is an object, the query method is not easy to use, and it is impossible to simply pass through node Value = = = value to judge. Because all objects in js are not equal, we still need to deal with the case that value is an object. Here I add a separate method. Although it can be distinguished by the number of parameters in find, I don't think it's clear enough. It's better to use one method for one purpose. Therefore, the code for processing object query is as follows, Here, you need to give an identification value of the object, such as {Name: "jack"}. The query method supports querying objects with name "jack". You need to pass key as "name" and value as "jack"

/**
 * query
 * @param {*} key Identification name of the node
 * @param {*} value Identification value of node
 * @param {*} all Whether to check all. false only queries the first one. true checks all and returns the value array
 * @returns Node value or node value array, no null value or empty array returned
 */
SingleLinkList.prototype.findObj = function (key, value, all = false) {
    let node = this.node;
    let nodeArr = [];
    while (node) {
        if (node.value[key] === value) {
            if (all) {
                node.push(node.value);
            } else {
                return node.value;
            }
        }
        node = node.next;
    }
    if (all) {
        return nodeArr;
    } else {
        return null;
    }
}

Insert, modify and delete

Like these methods, there are special methods for objects. Note that insertion is to insert a new node on the next of the target node
Go straight to the code

/**
 * Edit (the first qualified node is found by default)
 * @param {*} oldValue Node value to edit
 * @param {*} newValue New node value
 * @param {*} all Edit all qualified nodes
 * @returns Is the edit successful
 */
SingleLinkList.prototype.modify = function (oldValue, newValue, all = false) {
    try {
        if (oldValue && newValue) {
            let isModify = false; // Edited
            let node = this.head;
            while (node) {
                if (node.value === oldValue) {
                    node.value = newValue;
                    isModify = true;
                    if (!all) return true;
                }
                node = node.next;
            }
            if (isModify && all) return true;
        }
        return false;
    } catch (error) {
        throw new Error(error);
    }
}

/**
 * Edit (the first qualified node is found by default)
 * For value is the node of the object
 * @param {*} key Identification name of the node
 * @param {*} value Identification value of node
 * @param {*} newObj New node value
 * @returns Is the edit successful
 */
SingleLinkList.prototype.modifyObj = function (key, value, newObj, all = false) {
    try {
        if (key && value && newObj) {
            let isModify = false; // Edited
            let node = this.head;
            while (node) {
                if (node.value[key] === value) {
                    node.value = newObj;
                    isModify = true;
                    if (!all) return true;
                }
                node = node.next;
            }
            if (isModify && all) return true;
        }
        return false;
    } catch (error) {
        throw new Error(error);
    }
}

/**
 * Insert (put in the next position of the target node)
 * @param {*} positionValue Target node value (the first one found by default)
 * @param {*} insertValue Inserted node value
 * @param {*} all Whether to insert into all qualified target nodes next
 * @returns Insert successfully
 */
SingleLinkList.prototype.insert = function (positionValue, insertValue, all = false) {
    try {
        if (positionValue && insertValue) {
            let isInsert = false; // Has it been inserted
            let node = this.head;
            while (node) {
                if (node.value === positionValue) {
                    let newNode = new SingleNode(insertValue);
                    newNode.next = node.next;
                    node.next = newNode;
                    this.len++;
                    isInsert = true;
                    if (!all) return true;
                }
                node = node.next;
            }
            if (isInsert && all) return true;
        }
        return false;
    } catch (error) {
        throw new Error(error);
    }
}

/**
 * Insert (put in the next position of the target node)
 * For value is the node of the object
 * @param {*} positionValue Target node value (the first one found by default)
 * @param {*} insertValue Inserted node value
 * @param {*} all Whether to insert into all qualified target nodes next
 * @returns Insert successfully
 */
SingleLinkList.prototype.insertObj = function (key, value, newObj, all = false) {
    try {
        if (key && value && newObj) {
            let isInsert = false; // Has it been inserted
            let node = this.head;
            while (node) {
                if (node.value[key] === value) {
                    let newNode = new SingleNode(newObj);
                    newNode.next = node.next;
                    node.next = newNode;
                    this.len++;
                    isInsert = true;
                    if (!all) return true;
                }
                node = node.next;
            }
            if (isInsert && all) return true;
        }
        return false;
    } catch (error) {
        throw new Error(error);
    }
}

/**
 * delete
 * @param {*} value Value to delete
 * @param {*} all Delete all false delete only the first, true delete all
 * @returns true is returned if a single deletion is successful, no value is returned if multiple deletions are successful, and false is returned if no deletion value is found
 */
SingleLinkList.prototype.delete = function (value, all = false) {
    let node = this.head;
    let isDelete = false;
    if (node.value === value) {
        node = node.next;
        this.head = node;
        this.len--;
        isDelete = true;
        if (!all) return true;
    }
    while (node) {
        if (node.next && node.next.value === value) {
            node.next = node.next.next;
            this.len--;
            isDelete = true;
            if (!all) return true;
        }
        node = node.next;
    }
    if (isDelete && all) return true;
    return false;
}

/**
 * delete
 * For value is the node of the object
 * @param {*} key Identification name of the node
 * @param {*} value Identification value of node
 * @param {*} all Delete all false delete only the first, true delete all
 * @returns true is returned if a single deletion is successful, no value is returned if multiple deletions are successful, and false is returned if no deletion value is found
 */
 SingleLinkList.prototype.delete = function (key, value, all = false) {
    let node = this.head;
    let isDelete = false;
    if (node.value[key] === value) {
        node = node.next;
        this.head = node;
        this.len--;
        isDelete = true;
        if (!all) return true;
    }
    while (node) {
        if (node.next && node.next.value[key] === value) {
            node.next = node.next.next;
            this.len--;
            isDelete = true;
            if (!all) return true;
        }
        node = node.next;
    }
    if (isDelete && all) return true;
    return false;
}

javaScript complete code

Attach the full version of the javaScript code

// Unidirectional linked list node
function SingleNode(value) {
    let node = Object.create(null);
    node.value = value;  // Node value
    node.next = null;  // Next node
    return node;
}

/**
 * Unidirectional linked list
 * Constructor supports parameter passing
 * Parameter type: single parameter, variable length parameter, array
 * @example new SingleLinkList(1);
 * @example new SingleLinkList(1, 2, 3);
 * @example new SingleLinkList([1, 2, 3]);
 */
function SingleLinkList() {
    this.head = null; // Head node
    this.len = 0; // Linked list length

    // Add method in initialization method
    function initPush(param) {
        let node = new SingleNode(param);
        if (this.head) {
            node.next = this.head;
        }
        this.head = node;
        this.len++;
    }
    // Adding method of construction method
    function initInsert(param) {
        if (Object.prototype.toString.call(param) === "[object Array]") {
            for (let i = 0; i < param.length; i++) {
                initPush.call(this, param[i]);
            }
        } else {
            initPush.call(this, param);
        }
    }

    // initialization
    (function init(params) {
        switch (params.length) {
            case 0:
                break;
            // Node array or single node
            case 1:
                initInsert.call(this, params);
                break;
            // Variable length parameter, multiple nodes
            default:
                for (let i = 0; i < params.length; i++) {
                    initInsert.call(this, params[i]);
                }
                break;
        }
    }.bind(this, arguments))();


    /**
     * Get the length of the current linked list
     * @returns length
     */
    SingleLinkList.prototype.length = function () {
        return this.len;
    }

    /**
     * Add a node to the linked list and put it in the head
     * @param {*} value Node value
     */
    SingleLinkList.prototype.push = function (value) {
        let node = new SingleNode(value);
        if (this.head) {
            node.next = this.head;
        }
        this.head = node;
        this.len++;
    }

    /**
     * Pop up the last added node. If the linked list is empty, null will be returned
     * @returns Node value
     */
    SingleLinkList.prototype.pop = function () {
        if (this.head) {
            let node = this.head;
            this.head = node.next;
            this.len--;
            return node.value;
        }
        else {
            return null;
        }
    }

    /**
     * ergodic
     * @param {*} fn Callback function
     */
    SingleLinkList.prototype.finds = function (fn) {
        if (fn && Object.prototype.toString.call(fn) === "[object Function]") {
            let node = this.head;
            while (node) {
                fn(node.value);
                node = node.next;
            }
        } else {
            throw new Error("no callback function");
        }
    }

    /**
     * query
     * @param {*} value Value to query
     * @param {*} all Whether to check all. false only queries the first one. true checks all and returns the value array
     * @returns Node value or node value array, no null value or empty array returned
     */
    SingleLinkList.prototype.find = function (value, all = false) {
        let node = this.head;
        let nodeArr = [];
        while (node) {
            if (node.value === value) {
                if (all) {
                    nodeArr.push(node.value);
                } else {
                    return node.value;
                }
            }
            node = node.next;
        }
        if (all) {
            return nodeArr;
        } else {
            return null;
        }
    }

    /**
     * query
     * @param {*} key Identification name of the node
     * @param {*} value Identification value of node
     * @param {*} all Whether to check all. false only queries the first one. true checks all and returns the value array
     * @returns Node value or node value array, no null value or empty array returned
     */
    SingleLinkList.prototype.findObj = function (key, value, all = false) {
        let node = this.node;
        let nodeArr = [];
        while (node) {
            if (node.value[key] === value) {
                if (all) {
                    node.push(node.value);
                } else {
                    return node.value;
                }
            }
            node = node.next;
        }
        if (all) {
            return nodeArr;
        } else {
            return null;
        }
    }

    /**
     * Edit (the first qualified node is found by default)
     * @param {*} oldValue Node value to edit
     * @param {*} newValue New node value
     * @param {*} all Edit all qualified nodes
     * @returns Is the edit successful
     */
    SingleLinkList.prototype.modify = function (oldValue, newValue, all = false) {
        try {
            if (oldValue && newValue) {
                let isModify = false; // Edited
                let node = this.head;
                while (node) {
                    if (node.value === oldValue) {
                        node.value = newValue;
                        isModify = true;
                        if (!all) return true;
                    }
                    node = node.next;
                }
                if (isModify && all) return true;
            }
            return false;
        } catch (error) {
            throw new Error(error);
        }
    }

    /**
     * Edit (the first qualified node is found by default)
     * For value is the node of the object
     * @param {*} key Identification name of the node
     * @param {*} value Identification value of node
     * @param {*} newObj New node value
     * @returns Is the edit successful
     */
    SingleLinkList.prototype.modifyObj = function (key, value, newObj, all = false) {
        try {
            if (key && value && newObj) {
                let isModify = false; // Edited
                let node = this.head;
                while (node) {
                    if (node.value[key] === value) {
                        node.value = newObj;
                        isModify = true;
                        if (!all) return true;
                    }
                    node = node.next;
                }
                if (isModify && all) return true;
            }
            return false;
        } catch (error) {
            throw new Error(error);
        }
    }

    /**
     * Insert (put in the next position of the target node)
     * @param {*} positionValue Target node value (the first one found by default)
     * @param {*} insertValue Inserted node value
     * @param {*} all Whether to insert into all qualified target nodes next
     * @returns Whether the insertion succeeded or not
     */
    SingleLinkList.prototype.insert = function (positionValue, insertValue, all = false) {
        try {
            if (positionValue && insertValue) {
                let isInsert = false; // Has it been inserted
                let node = this.head;
                while (node) {
                    if (node.value === positionValue) {
                        let newNode = new SingleNode(insertValue);
                        newNode.next = node.next;
                        node.next = newNode;
                        this.len++;
                        isInsert = true;
                        if (!all) return true;
                    }
                    node = node.next;
                }
                if (isInsert && all) return true;
            }
            return false;
        } catch (error) {
            throw new Error(error);
        }
    }

    /**
     * Insert (put in the next position of the target node)
     * For value is the node of the object
     * @param {*} positionValue Target node value (the first one found by default)
     * @param {*} insertValue Inserted node value
     * @param {*} all Whether to insert into all qualified target nodes next
     * @returns Insert successfully
     */
    SingleLinkList.prototype.insertObj = function (key, value, newObj, all = false) {
        try {
            if (key && value && newObj) {
                let isInsert = false; // Has it been inserted
                let node = this.head;
                while (node) {
                    if (node.value[key] === value) {
                        let newNode = new SingleNode(newObj);
                        newNode.next = node.next;
                        node.next = newNode;
                        this.len++;
                        isInsert = true;
                        if (!all) return true;
                    }
                    node = node.next;
                }
                if (isInsert && all) return true;
            }
            return false;
        } catch (error) {
            throw new Error(error);
        }
    }

    /**
     * delete
     * @param {*} value Value to delete
     * @param {*} all Delete all false delete only the first, true delete all
     * @returns true is returned if a single deletion is successful, no value is returned if multiple deletions are successful, and false is returned if no deletion value is found
     */
    SingleLinkList.prototype.delete = function (value, all = false) {
        let node = this.head;
        let isDelete = false;
        if (node.value === value) {
            node = node.next;
            this.head = node;
            this.len--;
            isDelete = true;
            if (!all) return true;
        }
        while (node) {
            if (node.next && node.next.value === value) {
                node.next = node.next.next;
                this.len--;
                isDelete = true;
                if (!all) return true;
            }
            node = node.next;
        }
        if (isDelete && all) return true;
        return false;
    }

    /**
     * delete
     * For value is the node of the object
     * @param {*} key Identification name of the node
     * @param {*} value Identification value of node
     * @param {*} all Delete all false delete only the first, true delete all
     * @returns true is returned if a single deletion is successful, no value is returned if multiple deletions are successful, and false is returned if no deletion value is found
     */
     SingleLinkList.prototype.delete = function (key, value, all = false) {
        let node = this.head;
        let isDelete = false;
        if (node.value[key] === value) {
            node = node.next;
            this.head = node;
            this.len--;
            isDelete = true;
            if (!all) return true;
        }
        while (node) {
            if (node.next && node.next.value[key] === value) {
                node.next = node.next.next;
                this.len--;
                isDelete = true;
                if (!all) return true;
            }
            node = node.next;
        }
        if (isDelete && all) return true;
        return false;
    }
}

typeScript complete code

The code of typescribe version has not been completed yet. This part will be updated later

Insert code slice here

epilogue

Thank you for reading. If you have any questions, please correct them and hope to make progress with you

Topics: Javascript data structure linked list