GoScrobble/web/node_modules/string-natural-compare/natural-compare.js

140 lines
3.3 KiB
JavaScript
Raw Permalink Normal View History

2022-04-25 02:47:15 +00:00
'use strict';
const defaultAlphabetIndexMap = [];
function isNumberCode(code) {
return code >= 48/* '0' */ && code <= 57/* '9' */;
}
function naturalCompare(a, b, opts) {
if (typeof a !== 'string') {
throw new TypeError(`The first argument must be a string. Received type '${typeof a}'`);
}
if (typeof b !== 'string') {
throw new TypeError(`The second argument must be a string. Received type '${typeof b}'`);
}
const lengthA = a.length;
const lengthB = b.length;
let indexA = 0;
let indexB = 0;
let alphabetIndexMap = defaultAlphabetIndexMap;
let firstDifferenceInLeadingZeros = 0;
if (opts) {
if (opts.caseInsensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
if (opts.alphabet) {
alphabetIndexMap = buildAlphabetIndexMap(opts.alphabet);
}
}
while (indexA < lengthA && indexB < lengthB) {
let charCodeA = a.charCodeAt(indexA);
let charCodeB = b.charCodeAt(indexB);
if (isNumberCode(charCodeA)) {
if (!isNumberCode(charCodeB)) {
return charCodeA - charCodeB;
}
let numStartA = indexA;
let numStartB = indexB;
while (charCodeA === 48/* '0' */ && ++numStartA < lengthA) {
charCodeA = a.charCodeAt(numStartA);
}
while (charCodeB === 48/* '0' */ && ++numStartB < lengthB) {
charCodeB = b.charCodeAt(numStartB);
}
if (numStartA !== numStartB && firstDifferenceInLeadingZeros === 0) {
firstDifferenceInLeadingZeros = numStartA - numStartB;
}
let numEndA = numStartA;
let numEndB = numStartB;
while (numEndA < lengthA && isNumberCode(a.charCodeAt(numEndA))) {
++numEndA;
}
while (numEndB < lengthB && isNumberCode(b.charCodeAt(numEndB))) {
++numEndB;
}
let difference = numEndA - numStartA - numEndB + numStartB; // numA length - numB length
if (difference !== 0) {
return difference;
}
while (numStartA < numEndA) {
difference = a.charCodeAt(numStartA++) - b.charCodeAt(numStartB++);
if (difference !== 0) {
return difference;
}
}
indexA = numEndA;
indexB = numEndB;
continue;
}
if (charCodeA !== charCodeB) {
if (
charCodeA < alphabetIndexMap.length &&
charCodeB < alphabetIndexMap.length &&
alphabetIndexMap[charCodeA] !== -1 &&
alphabetIndexMap[charCodeB] !== -1
) {
return alphabetIndexMap[charCodeA] - alphabetIndexMap[charCodeB];
}
return charCodeA - charCodeB;
}
++indexA;
++indexB;
}
if (indexA < lengthA) { // `b` is a substring of `a`
return 1;
}
if (indexB < lengthB) { // `a` is a substring of `b`
return -1;
}
return firstDifferenceInLeadingZeros;
}
const alphabetIndexMapCache = {};
function buildAlphabetIndexMap(alphabet) {
const existingMap = alphabetIndexMapCache[alphabet];
if (existingMap !== undefined) {
return existingMap;
}
const indexMap = [];
const maxCharCode = alphabet.split('').reduce((maxCode, char) => {
return Math.max(maxCode, char.charCodeAt(0));
}, 0);
for (let i = 0; i <= maxCharCode; i++) {
indexMap.push(-1);
}
for (let i = 0; i < alphabet.length; i++) {
indexMap[alphabet.charCodeAt(i)] = i;
}
alphabetIndexMapCache[alphabet] = indexMap;
return indexMap;
}
module.exports = naturalCompare;