140 lines
3.3 KiB
JavaScript
140 lines
3.3 KiB
JavaScript
|
'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;
|