ArrayHelper.js

//@ts-check

/** 
 * A static class containing helper functions for array-related tasks. 
 */
class ArrayHelper {
    /**
     * Clone an array or an object. If an object is passed, a shallow clone will be created.
     *
     * @static
     * @param {*} arr The array or object to be cloned.
     * @returns {*} A clone of the array or object.
     */
    static clone(arr) {
        let out = Array.isArray(arr) ? Array() : {};
        
        for (let key in arr) {
            let value = arr[key];
            
            if (typeof value.clone === 'function') {
                out[key] = value.clone();
            }
            else {
                out[key] = (typeof value === 'object') ? ArrayHelper.clone(value) : value;
            }
        }
        
        return out;
    }

    /**
     * Returns a boolean indicating whether or not the two arrays contain the same elements.
     * Only supports 1d, non-nested arrays.
     *
     * @static
     * @param {Array} arrA An array.
     * @param {Array} arrB An array.
     * @returns {Boolean} A boolean indicating whether or not the two arrays contain the same elements.
     */
    static equals(arrA, arrB) {
        if (arrA.length !== arrB.length) {
            return false;
        }

        let tmpA = arrA.slice().sort();
        let tmpB = arrB.slice().sort();

        for (var i = 0; i < tmpA.length; i++) {
            if (tmpA[i] !== tmpB[i]) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns a string representation of an array. If the array contains objects with an id property, the id property is printed for each of the elements.
     *
     * @static
     * @param {Object[]} arr An array.
     * @param {*} arr[].id If the array contains an object with the property 'id', the properties value is printed. Else, the array elements value is printend.
     * @returns {String} A string representation of the array.
     */
    static print(arr) {
        if (arr.length == 0) {
            return '';
        }

        let s = '(';

        for (let i = 0; i < arr.length; i++) {
            s += arr[i].id ? arr[i].id + ', ' : arr[i] + ', ';
        }

        s = s.substring(0, s.length - 2);

        return s + ')';
    }

    /**
     * Run a function for each element in the array. The element is supplied as an argument for the callback function
     *
     * @static
     * @param {Array} arr An array.
     * @param {Function} callback The callback function that is called for each element.
     */
    static each(arr, callback) {
        for (let i = 0; i < arr.length; i++) {
            callback(arr[i]);
        }
    }

    /**
     * Return the array element from an array containing objects, where a property of the object is set to a given value.
     *
     * @static
     * @param {Array} arr An array.
     * @param {(String|Number)} property A property contained within an object in the array.
     * @param {(String|Number)} value The value of the property.
     * @returns {*} The array element matching the value.
     */
    static get(arr, property, value) {
        for (let i = 0; i < arr.length; i++) {
            if (arr[i][property] == value) {
                return arr[i];
            }
        }
    }

    /**
     * Checks whether or not an array contains a given value. the options object passed as a second argument can contain three properties. value: The value to be searched for. property: The property that is to be searched for a given value. func: A function that is used as a callback to return either true or false in order to do a custom comparison.
     *
     * @static
     * @param {Array} arr An array.
     * @param {Object} options See method description.
     * @param {*} options.value The value for which to check.
     * @param {String} [options.property=undefined] The property on which to check.
     * @param {Function} [options.func=undefined] A custom property function.
     * @returns {Boolean} A boolean whether or not the array contains a value.
     */
    static contains(arr, options) {
        if (!options.property && !options.func) {
            for (let i = 0; i < arr.length; i++) {
                if (arr[i] == options.value) {
                    return true;
                }
            }
        } else if (options.func) {
            for (let i = 0; i < arr.length; i++) {
                if (options.func(arr[i])) {
                    return true;
                }
            }
        } else {
            for (let i = 0; i < arr.length; i++) {
                if (arr[i][options.property] == options.value) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns an array containing the intersection between two arrays. That is, values that are common to both arrays.
     *
     * @static
     * @param {Array} arrA An array.
     * @param {Array} arrB An array.
     * @returns {Array} The intersecting vlaues.
     */
    static intersection(arrA, arrB) {
        let intersection = new Array();
        
        for (let i = 0; i < arrA.length; i++) {
            for (let j = 0; j < arrB.length; j++) {
                if (arrA[i] === arrB[j]) {
                    intersection.push(arrA[i]);
                }
            }
        }

        return intersection;
    }

    /**
     * Returns an array of unique elements contained in an array.
     *
     * @static
     * @param {Array} arr An array.
     * @returns {Array} An array of unique elements contained within the array supplied as an argument.
     */
    static unique(arr) {
        let contains = {};
        return arr.filter(function (i) {
            // using !== instead of hasOwnProperty (http://andrew.hedges.name/experiments/in/)
            return contains[i] !== undefined ? false : (contains[i] = true);
        });
    }

    /**
     * Count the number of occurences of a value in an array.
     *
     * @static
     * @param {Array} arr An array.
     * @param {*} value A value to be counted.
     * @returns {Number} The number of occurences of a value in the array.
     */
    static count(arr, value) {
        let count = 0;

        for (let i = 0; i < arr.length; i++) {
            if (arr[i] === value) {
                count++;
            }
        }

        return count;
    }

    /**
     * Toggles the value of an array. If a value is not contained in an array, the array returned will contain all the values of the original array including the value. If a value is contained in an array, the array returned will contain all the values of the original array excluding the value.
     *
     * @static
     * @param {Array} arr An array.
     * @param {*} value A value to be toggled.
     * @returns {Array} The toggled array.
     */
    static toggle(arr, value) {
        let newArr = Array();

        let removed = false;
        for (let i = 0; i < arr.length; i++) {
            // Do not copy value if it exists
            if (arr[i] !== value) {
                newArr.push(arr[i]);
            } else {
                // The element was not copied to the new array, which
                // means it was removed
                removed = true;
            }
        }

        // If the element was not removed, then it was not in the array
        // so add it
        if (!removed) {
            newArr.push(value);
        }

        return newArr;
    }

    /**
     * Remove a value from an array.
     *
     * @static
     * @param {Array} arr An array.
     * @param {*} value A value to be removed.
     * @returns {Array} A new array with the element with a given value removed.
     */
    static remove(arr, value) {
        let tmp = Array();

        for (let i = 0; i < arr.length; i++) {
            if (arr[i] !== value) {
                tmp.push(arr[i]);
            }
        }

        return tmp;
    }

    /**
     * Remove a value from an array with unique values.
     *
     * @static
     * @param {Array} arr An array.
     * @param {*} value A value to be removed.
     * @returns {Array} An array with the element with a given value removed.
     */
    static removeUnique(arr, value) {
        let index = arr.indexOf(value);

        if (index > -1) {
            arr.splice(index, 1);
        }

        return arr;
    }

    /**
     * Remove all elements contained in one array from another array.
     *
     * @static
     * @param {Array} arrA The array to be filtered.
     * @param {Array} arrB The array containing elements that will be removed from the other array.
     * @returns {Array} The filtered array.
     */
    static removeAll(arrA, arrB) {
        return arrA.filter(function (item) {
            return arrB.indexOf(item) === -1;
        });
    }

    /**
     * Merges two arrays and returns the result. The first array will be appended to the second array.
     *
     * @static
     * @param {Array} arrA An array.
     * @param {Array} arrB An array.
     * @returns {Array} The merged array.
     */
    static merge(arrA, arrB) {
        let arr = new Array(arrA.length + arrB.length);

        for (let i = 0; i < arrA.length; i++) {
            arr[i] = arrA[i];
        }

        for (let i = 0; i < arrB.length; i++) {
            arr[arrA.length + i] = arrB[i];
        }

        return arr;
    }

    /**
     * Checks whether or not an array contains all the elements of another array, without regard to the order.
     *
     * @static
     * @param {Array} arrA An array.
     * @param {Array} arrB An array.
     * @returns {Boolean} A boolean indicating whether or not both array contain the same elements.
     */
    static containsAll(arrA, arrB) {
        let containing = 0;
        for (let i = 0; i < arrA.length; i++) {
            for (let j = 0; j < arrB.length; j++) {
                if (arrA[i] === arrB[j]) {
                    containing++;
                }
            }
        }

        return containing === arrB.length;
    }
    
    /**
     * Sort an array of atomic number information. Where the number is indicated as x, x.y, x.y.z, ...
     *
     * @param {Object[]} arr An array of vertex ids with their associated atomic numbers.
     * @param {Number} arr[].vertexId A vertex id.
     * @param {String} arr[].atomicNumber The atomic number associated with the vertex id.
     * @returns {Object[]} The array sorted by atomic number. Example of an array entry: { atomicNumber: 2, vertexId: 5 }.
     */
    static sortByAtomicNumberDesc(arr) {
        let map = arr.map(function(e, i) {
            return { index: i, value: e.atomicNumber.split('.').map(Number) };
        });

        map.sort(function(a, b) {
            let min = Math.min(b.value.length, a.value.length);
            let i = 0;
            
            while(i < min && b.value[i] === a.value[i]) {
                i++;
            }

            return i === min ? b.value.length - a.value.length : b.value[i] - a.value[i];
        });

        return map.map(function(e) {
            return arr[e.index];
        });
    }

    /**
     * Copies a an n-dimensional array.
     * 
     * @param {Array} arr The array to be copied.
     * @returns {Array} The copy.
     */
    static deepCopy(arr) {
        let newArr = Array();

        for (let i = 0; i < arr.length; i++) {
            let item = arr[i];

            if (item instanceof Array) {
                newArr[i] = ArrayHelper.deepCopy(item);
            } else {
                newArr[i] = item;
            }
        }

        return newArr;
    }

}

module.exports = ArrayHelper;