move.js

/**
 * 
 * @class Utility class that provides methods to move Array elements into a specific order.
 * @hideconstructor
 *
 * @copyright (c) 2021 TLF Research Ltd.
 * 
 */
function Move() { }

/**
 * Returns a new array with all the array elements that match the predicate moved to the end of the array.
 * Moved elements are appended in the order in which they appear in the original array.
 * The original array is unchanged.
 *
 * @example
 * // b becomes [{"prop": "", "val": 1}, {"prop": "2", "val": 4}, {"prop": null, "val": 2}, {"prop": "10", "val": 3}]
 * const a = [{"prop": "", "val": 1}, {"prop": null, "val": 2}, {"prop": "10", "val": 3}, {"prop": "2", "val": 4}];
 * const b = Move.toEnd(a, el => el.val > 1 && el.val < 4);

 * @param {array} arr Array to examine
 * @param {function} predicate Filter method that is called for each element in the array. It should return true if the array element is to be moved.
 *
 * @return {array}
 */
Move.toEnd = function (arr, predicate) {
    const stack = [];
    const lastIndex = arr.length - 1;

    return arr.reduce((acc, a, idx) => {
        if (predicate(a)) {
            stack.push(a);
        } else {
            acc.push(a);
        }
        if (idx === lastIndex) {
            acc.push(...stack);
        }

        return acc;
    }, []);
};


/**
 * Returns a new array with all all array elements moved into the specified order.
 * Elements with identical object property values are moved together i.e retain their original relative order.
 * Any unordered values from the original array are appended to the end of the new array.
 * The original array is unchanged.
 * 
 * @example
 * // b is [{"prop": "", "val": 1}, {"prop": "2", "val": 4}, {"prop": null, "val": 2}, {"prop": "10", "val": 3}]
 * const a = [{"prop": "10", "val": 3}, {"prop": "", "val": 1}, {"prop": "2", "val": 4}, {"prop": null, "val": 2}];
 * const b = Move.inOrder(a, "val", [3, 1, 4, 2]);
 *
 * @param {array} arr Array of objects to examine
 * @param {string} property Object property to use for the reorder.
 * @param {array} valueOrder Desired order of property values.
 *
 * @return {array}
 */
Move.inOrder = function (arr, property, valueOrder) {
    const result = [];
    const reordered = [];

    valueOrder.forEach(v => {
        for (let idx = 0; idx < arr.length; idx++) {
            const obj = arr[idx];
            // Use == not === here as we want to match numerics with strings and vice-versa
            if (typeof obj[property] !== 'undefined' && obj[property] == v) {
                result.push(obj);
                reordered.push(idx);
            }
        }
    });

    // Add any unordered values in the original array to the end of the result
    result.push(...arr.filter((v, idx) => reordered.indexOf(idx) === -1));

    return result;
}