locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
361 lines (360 loc) • 14.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.array_multisort = array_multisort;
const flags = {
SORT_REGULAR: 16,
SORT_NUMERIC: 17,
SORT_STRING: 18,
SORT_ASC: 32,
SORT_DESC: 40,
};
const isSortableObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
const isSortFlag = (value) => typeof value === 'string' && value in flags;
const isComparablePrimitive = (value) => typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'bigint' ||
typeof value === 'boolean' ||
value instanceof Date;
const copyBackToObject = (target, values, keys) => {
for (const key in target) {
if (Object.hasOwn(target, key)) {
delete target[key];
}
}
for (let index = 0; index < values.length; index++) {
const key = keys[index];
if (key === undefined) {
continue;
}
target[key] = values[index];
}
};
const compareRegular = (leftValue, rightValue) => {
if (leftValue === rightValue) {
return 0;
}
if (leftValue === null || leftValue === undefined) {
return -1;
}
if (rightValue === null || rightValue === undefined) {
return 1;
}
if (isComparablePrimitive(leftValue) && isComparablePrimitive(rightValue)) {
return leftValue > rightValue ? 1 : -1;
}
const left = String(leftValue);
const right = String(rightValue);
return left > right ? 1 : left < right ? -1 : 0;
};
function array_multisort(arr, ...rest) {
// discuss at: https://locutus.io/php/array_multisort/
// original by: Theriault (https://github.com/Theriault)
// improved by: Oleg Andreyev (https://github.com/oleg-andreyev)
// example 1: array_multisort([1, 2, 1, 2, 1, 2], [1, 2, 3, 4, 5, 6])
// returns 1: true
// example 2: var $characters = {A: 'Edward', B: 'Locke', C: 'Sabin', D: 'Terra', E: 'Edward'}
// example 2: var $jobs = {A: 'Warrior', B: 'Thief', C: 'Monk', D: 'Mage', E: 'Knight'}
// example 2: array_multisort($characters, 'SORT_DESC', 'SORT_STRING', $jobs, 'SORT_ASC', 'SORT_STRING')
// returns 2: true
// example 3: var $lastnames = [ 'Carter','Adams','Monroe','Tyler','Madison','Kennedy','Adams']
// example 3: var $firstnames = ['James', 'John' ,'James', 'John', 'James', 'John', 'John']
// example 3: var $president = [ 39, 6, 5, 10, 4, 35, 2 ]
// example 3: array_multisort($firstnames, 'SORT_DESC', 'SORT_STRING', $lastnames, 'SORT_ASC', 'SORT_STRING', $president, 'SORT_NUMERIC')
// returns 3: true
// example 4: array_multisort(["productIds[]", "_"], 'SORT_ASC', ["productIds[]=977385529", "_=1502965788347"])
// returns 4: true
// note 1: flags: Translation table for sort arguments.
// note 1: Each argument turns on certain bits in the flag byte through addition.
// note 1: bits: HGFE DCBA
// note 1: args: Holds pointer to arguments for reassignment
let i = 0;
let j = 0;
let l = 0;
let sal = 0;
let vkey = '';
let elIndex = 0;
let zlast;
const sortFlag = [0];
const thingsToSort = [];
let nLastSort = [];
let lastSort = [];
const args = [arr, ...rest];
const sortDuplicator = function () {
return nLastSort.shift() ?? 0;
};
const sortFunctions = [
[
function (a, b) {
const result = compareRegular(a, b);
lastSort.push(result);
return result;
},
function (a, b) {
const result = compareRegular(b, a);
lastSort.push(result);
return result;
},
],
[
function (a, b) {
const result = Number(a) - Number(b);
lastSort.push(result);
return result;
},
function (a, b) {
const result = Number(b) - Number(a);
lastSort.push(result);
return result;
},
],
[
function (a, b) {
const left = String(a);
const right = String(b);
const result = left > right ? 1 : left < right ? -1 : 0;
lastSort.push(result);
return result;
},
function (a, b) {
const left = String(b);
const right = String(a);
const result = left > right ? 1 : left < right ? -1 : 0;
lastSort.push(result);
return result;
},
],
];
const sortArrs = [[]];
const sortKeys = [[]];
// Store first argument into sortArrs and sortKeys if an Object.
// First Argument should be either a Javascript Array or an Object,
// otherwise function would return FALSE like in PHP
if (Array.isArray(arr)) {
sortArrs[0] = arr;
}
else if (isSortableObject(arr)) {
const firstSortKeys = sortKeys[0];
const firstSortValues = sortArrs[0];
if (!firstSortKeys || !firstSortValues) {
return false;
}
for (const key in arr) {
if (Object.hasOwn(arr, key)) {
firstSortKeys.push(key);
firstSortValues.push(arr[key]);
}
}
}
else {
return false;
}
// arrMainLength: Holds the length of the first array.
// All other arrays must be of equal length, otherwise function would return FALSE like in PHP
// sortComponents: Holds 2 indexes per every section of the array
// that can be sorted. As this is the start, the whole array can be sorted.
const primarySortArray = sortArrs[0];
if (!primarySortArray) {
return false;
}
const arrMainLength = primarySortArray.length;
let sortComponents = [0, arrMainLength];
// Loop through all other arguments, checking lengths and sort flags
// of arrays and adding them to the above variables.
const argl = args.length;
for (j = 1; j < argl; j++) {
const arg = args[j];
if (Array.isArray(arg)) {
sortArrs[j] = arg;
sortFlag[j] = 0;
if (arg.length !== arrMainLength) {
return false;
}
}
else if (isSortableObject(arg)) {
const currentSortKeys = (sortKeys[j] = []);
const currentSortValues = (sortArrs[j] = []);
sortFlag[j] = 0;
for (const key in arg) {
if (Object.hasOwn(arg, key)) {
currentSortKeys.push(key);
currentSortValues.push(arg[key]);
}
}
if (currentSortValues.length !== arrMainLength) {
return false;
}
}
else if (typeof arg === 'string') {
if (!isSortFlag(arg)) {
return false;
}
const lFlag = sortFlag.pop() ?? 0;
// Keep extra parentheses around latter flags check
// to avoid minimization leading to CDATA closer
if (((flags[arg] >>> 4) & (lFlag >>> 4)) > 0) {
return false;
}
sortFlag.push(lFlag + flags[arg]);
}
else {
return false;
}
}
for (i = 0; i !== arrMainLength; i++) {
thingsToSort.push(true);
}
// Sort all the arrays....
for (const iKey in sortArrs) {
if (Object.hasOwn(sortArrs, iKey)) {
const iNum = Number(iKey);
const lastSorts = [];
let tmpArray = [];
elIndex = 0;
nLastSort = [];
lastSort = [];
const currentSortArr = sortArrs[iNum];
if (!currentSortArr) {
continue;
}
// If there are no sortComponents, then no more sorting is neeeded.
// Copy the array back to the argument.
if (sortComponents.length === 0) {
const arg = args[iNum];
if (Array.isArray(arg)) {
args[iNum] = currentSortArr;
}
else if (isSortableObject(arg)) {
const currentSortKeySet = sortKeys[iNum] ?? [];
sal = currentSortArr.length;
for (j = 0, vkey = ''; j < sal; j++) {
vkey = currentSortKeySet[j] ?? '';
if (vkey !== '') {
arg[vkey] = currentSortArr[j];
}
}
}
sortArrs.splice(iNum, 1);
sortKeys.splice(iNum, 1);
continue;
}
// Sort function for sorting. Either sorts asc or desc, regular/string or numeric.
const currentSortFlag = sortFlag[iNum] ?? 0;
const functionGroup = sortFunctions[currentSortFlag & 3];
if (!functionGroup) {
return false;
}
let sFunction = functionGroup[(currentSortFlag & 8) > 0 ? 1 : 0] ?? functionGroup[0];
// Sort current array.
for (l = 0; l !== sortComponents.length; l += 2) {
const componentStart = sortComponents[l];
const componentEnd = sortComponents[l + 1];
if (componentStart === undefined || componentEnd === undefined) {
continue;
}
tmpArray = currentSortArr.slice(componentStart, componentEnd + 1);
tmpArray.sort(sFunction);
// Is there a better way to copy an array in Javascript?
lastSorts[l] = [...lastSort];
elIndex = componentStart;
for (const value of tmpArray) {
currentSortArr[elIndex] = value;
elIndex++;
}
}
// Duplicate the sorting of the current array on future arrays.
sFunction = sortDuplicator;
for (const jKey in sortArrs) {
if (Object.hasOwn(sortArrs, jKey)) {
const jNum = Number(jKey);
const targetSortArr = sortArrs[jNum];
if (!targetSortArr || targetSortArr === currentSortArr) {
continue;
}
for (l = 0; l !== sortComponents.length; l += 2) {
const componentStart = sortComponents[l];
const componentEnd = sortComponents[l + 1];
if (componentStart === undefined || componentEnd === undefined) {
continue;
}
tmpArray = targetSortArr.slice(componentStart, componentEnd + 1);
// alert(l + ':' + nLastSort);
nLastSort = [...(lastSorts[l] ?? [])];
tmpArray.sort(sFunction);
elIndex = componentStart;
for (const value of tmpArray) {
targetSortArr[elIndex] = value;
elIndex++;
}
}
}
}
// Duplicate the sorting of the current array on array keys
for (const jKey in sortKeys) {
if (Object.hasOwn(sortKeys, jKey)) {
const jNum = Number(jKey);
const targetSortKeys = sortKeys[jNum];
if (!targetSortKeys) {
continue;
}
for (l = 0; l !== sortComponents.length; l += 2) {
const componentStart = sortComponents[l];
const componentEnd = sortComponents[l + 1];
if (componentStart === undefined || componentEnd === undefined) {
continue;
}
tmpArray = targetSortKeys.slice(componentStart, componentEnd + 1);
nLastSort = [...(lastSorts[l] ?? [])];
tmpArray.sort(sFunction);
elIndex = componentStart;
for (const value of tmpArray) {
targetSortKeys[elIndex] = String(value);
elIndex++;
}
}
}
}
// Generate the next sortComponents
zlast = null;
sortComponents = [];
let lastIndex = 0;
for (let idx = 0; idx < currentSortArr.length; idx++) {
lastIndex = idx;
if (!thingsToSort[idx]) {
if (sortComponents.length & 1) {
sortComponents.push(idx - 1);
}
zlast = null;
continue;
}
if (!(sortComponents.length & 1)) {
if (zlast !== null) {
if (currentSortArr[idx] === zlast) {
sortComponents.push(idx - 1);
}
else {
thingsToSort[idx] = false;
}
}
zlast = currentSortArr[idx];
}
else if (currentSortArr[idx] !== zlast) {
sortComponents.push(idx - 1);
zlast = currentSortArr[idx];
}
}
if (sortComponents.length & 1) {
sortComponents.push(lastIndex);
}
const arg = args[iNum];
if (Array.isArray(arg)) {
args[iNum] = currentSortArr;
}
else if (isSortableObject(arg)) {
copyBackToObject(arg, currentSortArr, sortKeys[iNum] ?? []);
}
sortArrs.splice(iNum, 1);
sortKeys.splice(iNum, 1);
}
}
return true;
}