lib.js

/**
 * Returns sum of all number items in the given array using recursion. 
 * Works with any array nesting. Does not calculates not number types.
 * @param {Array} array Array to sum items 
 * @returns {number} Sum of all items in the given array.
 * 
 * @example
 * 
 * const arr_1 = [1, 2, 3];
 * 
 * console.log(arraySum(arr_1));
 * 
 * // => 6
 * 
 * const arr_2 = ['hello', true, null, [10, [-5, 4]]];
 * 
 * console.log(arraySum(arr_2));
 * 
 * // => 9
 */

function arraySum(array) {
    let sum = 0;

    for (const item of array) {
        if (Array.isArray(item)) {
            sum += arraySum(item);
        }

        if (typeof item === 'number') {
            sum += item;
        }
    }

    return sum;
}
/**
 * Sorts the array using bubble sort O(n²).
 * Mutates the original array.
 * Do not recommend use this algorightm in the projects apart from educational ones.
 * @param {Array} array Array to sort. 
 * @returns {Array} Ascending sorted array.
 * 
 * @example
 * 
 * const arr = [5, 3, 1, 9, 0];
 * 
 * console.log(bubbleSort(arr));
 * // => [0, 1, 3, 5, 9]
 * 
 * console.log(arr);
 * // => [0, 1, 3, 5, 9]
 * 
 */

const bubbleSort = (array) => {
    let stepsCount = array.length - 1;

    let swapped;

    do {
        swapped = false;

        for (let i = 0; i < stepsCount; i += 1) {
            if (array[i] > array[i + 1]) {
                const temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }

            swapped = true;
        }
        stepsCount -= 1;

    } while (swapped);

    return array;
};
/**
 * Creates an array of elements split into groups the length of size. If array can't be split evenly, the 
 * final chunk will be the remaining elements.
 * @param {Array} array The array to chunk.
 * @param {number} [size = 1]  The length of each chunk. 
 * @returns {Array} Returns the new chunked array.
 * 
 * @example
 * 
 * console.log(chunk(['a', 'b', 'c', 'd'], 2));
 * 
 * // => [ [ 'a', 'b' ], [ 'c', 'd' ] ]
 * 
 * console.log(chunk(['a', 'b', 'c', 'd'], 3));
 * 
 * // => [ [ 'a', 'b', 'c' ], [ 'd' ] ]
 */

function chunk(array, size = 1) {
    const newArr = [];

   for (let i = 0, j = 0; i < array.length; i += size, j += 1) {
       newArr[j] = array.slice(i, i + size);
   }

   return newArr;
}

console.log(chunk(['a', 'b', 'c', 'd'], 3));/**
 * Recursively clones the given object and returns a deep copy of the object.
 * @param {Object} object Object to copy
 * @returns {Object} Returns the deep clone of the given object
 * 
 * @example
 * 
 * const object_1 = {
 *   a: 1,
 *   b: 2,
 *   c: {
 *       d: {
 *           e: 4,
 *       },
 *   },
 * };
 *
 * const object_2 = cloneDeep(object_1);
 *
 * console.log(object_1 === object_2)
 * // => false
 *
 * console.log(object_1.c.d === object_2.c.d);
 * // => false
 * 
 */

function cloneDeep(object) {
    const newObject = {};

    for (const [key, value] of Object.entries(object)) {
        newObject[key] = typeof value === 'object' ? cloneDeep(value) : value;
    }

    return newObject;
}

const sjs = {};

sjs._cloneDeep = cloneDeep;

sjs._cloneDeep()/**
 * Makes the new array from others arrays.
 * @param {Array} array The array to concatenate.
 * @param  {any} [items] The values to concatenate.
 * @returns {Array} Returns the new concatenated array.
 * 
 * @example
 * 
 * const array_1 = [1, 2, 3];
 * const array_2 = concat(array_1, 4, 5, 6, [7, 8, [9]])
 * 
 * console.log(array_2);
 * // => [1, 2, 3, 4, 5, 6, 7, 8, [9]]
 * 
 * console.log(array_1);
 * // => [1, 2, 3]
 */

function concat(array, ...items) {
    const newArr = [];

    for (let i = 0; i < array.length; i += 1) {
        newArr[i] = array[i];
    }

    for (let i = 0, j = newArr.length; i < items.length; i += 1, j += 1) {
        if (items[i].constructor === Array) {
            for (let k = 0; k < items[i].length; k += 1) {
                newArr[j] = items[i][k];
                if (k !== items[i].length - 1) {
                    j++;
                }
            }
        } else {
            newArr[j] = items[i];
        }
    }

    return newArr;
}
/**
 * Create an object that includes count of all letters in the given string.
 * Does not counts spaces.
 * @param {string} string String to count letters.
 * @returns {Object} Object with values of letters.
 * 
 * @example
 * 
 * const string = 'Hello wonderful world!';
 * 
 * console.log(countLetters(string));
 * // => { H: 1, e: 2, l: 4, o: 3, w: 2, n: 1, d: 2, r: 2, f: 1, u: 1, '!': 1 }
 */

function countLetters(string) {
    const chars = {};

    for (const char of string) {
        if (char === ' ') continue;
        chars[char] = 1 + (chars[char] || 0);
    }

    return chars;
}
/**
 * Creates an array of array values not included in the other 
 * given arrays.
 * @param {Array} array_1 First array.
 * @param {Array} array_2 Second array.
 * @returns {Array}  Returns the new array of filtered values.
 * 
 * @example
 * 
 * console.log(diff([1, 2, 3], [3, 4, 5]));
 * // => [1, 2]
 */

function diff(array_1, array_2) {
    return array_1.filter(item => !array_2.includes(item));
}

/**
 * Returns the elements of an array that meet the condition specified in a callback function.
 * @param {Array} array array to filter.
 * @param {Function} callback callback function calling for each item in the array. Syntax: currentValue[, index[, array]]
 * @returns {Array} filtered array.
 * 
 * @example
 * 
 * const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 10];
 * 
 * console.log(filter(numbers, item => item % 2 === 0));
 * // => [2, 4, 6, 8, 10]
 */
function filter(array, callback) {
    const newArr = [];

    for (let i = 0; i < array.length; i += 1) {
        if (callback(array[i], i, array)) {
            console.log(i)
            newArr.push(array[i]);
        }
    }

    return newArr;
}

/**
 * Recursively flattens the given array and returns one-dimensional array (or single dimension array).
 * @param {Array} array Array to flat.
 * @returns {Array} One-dimensional
 * 
 * @example
 * 
 * const a = [1, 2, [3, 4], 5, [[[[[6]]],],]];
 * 
 * console.log(flatten(a));
 * // => [ 1, 2, 3, 4, 5, 6 ]
 */
function flattenDeep(array) {
    const result = [];

    for (const item of array) {
        if (Array.isArray(item)) {
            const newArr = flatten(item);
            result.push(...newArr);
        }   else {
            result.push(item);

        }
    }

    return result;
}
/**
 * Create an object using values in the array.
 * @param {Array} pairs Array of arrays, including pairs. 
 * @returns {Object} Object created by the given array.
 * 
 * @example
 * 
 * const person = [['name', 'Steve'], ['age', 32], ['isAdmin', false]];
 * 
 * console.log(fromPairs(person));
 * // => { name: 'Steve', age: 32, isAdmin: false }
 */

function fromPairs(pairs) {
    const object = {};

    for (const [key, value] of pairs) {
        object[key] = value;
    }

    return object;
}
/**
 * Returns the distance between two points in 2D (map)
 * @param {Array<number>} point_1 The first point.
 * @param {Array<number>} point_2 The second point.
 * @returns {number} distance
 * 
 * @example
 * 
 * console.log(getDistance2D([6, 5], [6, 6]));
 * // => 1
 * console.log(getDistance2D([0, 0], [10, 0]))
 * // => 10
 */

function getDistance2D([x1, y1], [x2, y2]) {
    const xs = x2 - x1;
    const ys = y2 - y1;

    return Math.sqrt(xs ** 2 + ys ** 2);
}

console.log(getDistance2D([0, 0], [10, 0]))/**
 * Returns the distance between two points in 3D.
 * @param {Array<number>} point_1 The first point.
 * @param {Array<number>} point_2 The second point.
 * @returns {number} Distance.
 *
 * @example
 *
 * console.log(getDistance3D([0, -3, 3], [3, 1, 3]));
 * // => 5
 */
function getDistance3D([x1, y1, z1], [x2, y2, z2]) {
    const xs = x2 - x1;
    const ys = y2 - y1;
    const zs = z2 - z1;

    return Math.sqrt(xs ** 2 + ys ** 2 + zs **2 );
}

console.log(getDistance3D([5, 0, 6], [0, 1, 8]));/**
 * Check if the array includes the search element and returns
 * boolean value. Correctly works with NaN using Object.is() method.
 * 
 * @param {Array} array Array for searching.
 * @param {any} searchElement Element to search.
 * @param {number} [fromIndex] The index where the search starts from.
 * @returns {boolean} Result of the search.
 * 
 * @example
 * 
 * includes(['apple', 'banana', 'pear'], 'pear')
 * // => true
 */

function includes(array, searchElement, fromIndex = 0) {
    if (fromIndex < 0) {
        fromIndex = array.length + fromIndex;
    }

    if (fromIndex % 1 !== 0) {
        fromIndex = Math.trunc(fromIndex);
    }

    for (let i = fromIndex; i < array.length; i += 1) {
        if (Object.is(array[i], searchElement)) {
            return true;
        }
    }

    return false;
}
/**
 * Creates an array of unique values that are includes in two
 * given arrays.
 * @param {Array} array_1 First array.
 * @param {Array} array_2 Second array.
 * @returns {Array} Returns the new array of intersecting values.
 * 
 * @example
 * 
 * console.log(intersection([1, 2, 3, 4], [3, 4, 5, 6]));
 * // => [3, 4]
 */

function intersection(array_1, array_2) {
    return array_1.filter(item => array_2.includes(item));
}
/**
 * Calls a defined callback function on each element of an array, and returns an array that contains the results.
 * @param {Array} array Array to call callback function.
 * @param {Function} callback Function that calling for each item in the array and returns true/false. Syntax: currentValue[, index[, array]]
 * @returns {Array} Mapped array.
 * 
 * @example
 * const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 10];
 *
 * console.log(map(numbers, item => item * 2));
 * // => [
   2,  4,  6,  8, 10,
  12, 14, 16, 20
]
 */

function map(array, callback) {
    const newArr = [];

    for (let i = 0; i < array.length; i += 1) {
        if (i in array) {
            newArr[i] = callback(array[i], i, array);
        }
    }

    return newArr;
}

console.log([1, 2, 3, 4, 5, 6, 7, 8, 10].map(item => item * 2));/**
 * Deletes the last element in the array and return deleted element
 * @param {Array} array array
 * @returns {any} deleted element
 * 
 * @example
 * 
 * const arr = [1, 2, 3, 4, 5];
 * 
 * console.log(pop(arr));
 * // => 5;
 * 
 * console.log(arr);
 * // => [1, 2, 3, 4]
 */

function pop(array) {
    const deletedItem = array[array.length - 1];

    array.length--;

    return deletedItem;

}/**
 * Add the elements in the end of given array and returns its new length.
 * @param {Array} array Array to add elements.
 * @param  {any} elements To add in the end of array.
 * @returns {number} New length.
 * 
 * @example
 * 
 * const fruits = ['apple', 'banana', 'pear'];
 * 
 * console.log(push(fruits, 'grape', 'kiwi'));
 * // => 5
 * 
 * console.log(fruits);
 * // => ['apple', 'banana', 'pear', 'grape', 'kiwi']
 */

function push(array, ...elements) {
    const newLength = elements.length + array.length;

    for (let i = array.length, j = 0; i < newLength; i += 1, j += 1) {
        array[i] = elements[j];
    }

    return newLength;
}/**
 * Deletes the first item to the beginning of the array.
 * @param {Array} array Array to delete the first item.
 * @returns {any} Deleted item to the beginning of the array.
 * 
 * @example
 * 
 * const arr = [true, false, 0, 5];
 * 
 * console.log(shift(arr));
 * // => true
 * 
 * console.log(arr);
 * // => [false, 0, 5]
 */

function shift(array) {
    const deletedItem = array[0];

    for (let i = 1, j = 0; i < array.length; i += 1, j += 1) {
        array[j] = array[i];
    }

    array.length--;

    return deletedItem;
}
/**
 * Shuffles the given array and returns it.
 * Uses Fisher-Yates (aka Knuth) Shuffle.
 * Mutates the original array.
 * 
 * For mor details: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
 * 
 * @example
 * 
 * const array = [0, 1, 2, 3, 4, 5];
 * 
 * console.log(shuffleArray(array));
 * // => [
  3, 8, 6, 2, 5,
  1, 7, 0, 4, 9
]
 * 
 * @param {Array} array Array to shuffle.
 * @returns {Array} Shuffled array
 */

function shuffleArray(array) {
    let currentIndex = array.length, randomIndex;

    // While there remain elements to shuffle...
    while (currentIndex != 0) {

        // Pick a remaining element...
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex--;

        // And swap it with the current element.
        [array[currentIndex], array[randomIndex]] = [
            array[randomIndex], array[currentIndex]];
    }

    return array;
}
/**
 * Add elements to the the begginning of the array.
 * @param {Array} array Array to add.
 * @param  {any} elements To add to the begginning of the array.
 * @returns {number} New length of the array.
 */
function unshift(array, ...elements) {
    let newLength = array.length + elements.length;

    for (let i = elements.length, j = 0; i < newLength; i += 1, j += 1) {
        elements[i] = array[j];
    }

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

    return newLength;
}