sortering - Naturlig sortering av alfanumeriska strängar i JavaScript


Jag letar efter det enklaste sättet att sortera en array som består av siffror och text, och en kombination av dessa.

T.ex.

'123asd'
'19asd'
'12345asd'
'asd123'
'asd12'

förvandlas till

'19asd'
'123asd'
'12345asd'
'asd12'
'asd123'

Detta kommer att användas i kombination med lösningen på en annan fråga jag har ställt här.

Själva sorteringsfunktionen fungerar, det jag behöver är en funktion som kan säga att '19asd' är mindre än '123asd'.

Jag skriver detta i JavaScript.

Jag letar efter en funktion för naturlig sortering.

Svar #1

Detta är nu möjligt i moderna webbläsare med hjälp av localeCompare. Genom att använda alternativet numeric: true kommer det smart att känna igen nummer. Du kan göra det skiftlägesokänsligt med sensitivity: 'base'. Det testades i Chrome, Firefox och Internet Explorer 11.

Här är ett exempel. Det returnerar 1, vilket betyder att 10 kommer efter 2:

'10'.localeCompare('2', undefined, {numeric: true, sensitivity: 'base'})

För prestanda vid sortering av stora mängder strängar säger artikeln:

När man jämför stora mängder strängar, såsom vid sortering av stora arrayer, är det bättre att skapa ett Intl.Collator-objekt och använda funktionen som tillhandahålls av dess compare-egenskap.

var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
var myArray = ['1_Document', '11_Document', '2_Document'];
console.log(myArray.sort(collator.compare));

Svar #2

Om du har en array av objekt kan du göra det på det här sättet:

myArrayObjects = myArrayObjects.sort(function(a, b) {
  return a.name.localeCompare(b.name, undefined, {
    numeric: true,
    sensitivity: 'base'
  });
});

var myArrayObjects = [{
    "id": 1,
    "name": "1 example"
  },
  {
    "id": 2,
    "name": "100 example"
  },
  {
    "id": 3,
    "name": "12 example"
  },
  {
    "id": 4,
    "name": "5 example"
  },

]

myArrayObjects = myArrayObjects.sort(function(a, b) {
  return a.name.localeCompare(b.name, undefined, {
    numeric: true,
    sensitivity: 'base'
  });
});
console.log(myArrayObjects);

Svar #3

För att jämföra värden kan du använda en jämförelsemetod-

function naturalSorter(as, bs){
    var a, b, a1, b1, i= 0, n, L,
    rx=/(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.\D+)|(\.$)/g;
    if(as=== bs) return 0;
    a= as.toLowerCase().match(rx);
    b= bs.toLowerCase().match(rx);
    L= a.length;
    while(i<L){
        if(!b[i]) return 1;
        a1= a[i],
        b1= b[i++];
        if(a1!== b1){
            n= a1-b1;
            if(!isNaN(n)) return n;
            return a1>b1? 1:-1;
        }
    }
    return b[i]? -1:0;
}

Men för hastighet vid sortering av en array, rigga arrayen innan sortering, så att du bara behöver göra konverteringar till gemener och reguljära uttrycket en gång istället för i varje steg genom sorteringen.

function naturalSort(ar, index){
    var L= ar.length, i, who, next, 
    isi= typeof index== 'number', 
    rx=  /(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.(\D+|$))/g;
    function nSort(aa, bb){
        var a= aa[0], b= bb[0], a1, b1, i= 0, n, L= a.length;
        while(i<L){
            if(!b[i]) return 1;
            a1= a[i];
            b1= b[i++];
            if(a1!== b1){
                n= a1-b1;
                if(!isNaN(n)) return n;
                return a1>b1? 1: -1;
            }
        }
        return b[i]!= undefined? -1: 0;
    }
    for(i= 0; i<L; i++){
        who= ar[i];
        next= isi? ar[i][index] || '': who;
        ar[i]= [String(next).toLowerCase().match(rx), who];
    }
    ar.sort(nSort);
    for(i= 0; i<L; i++){
        ar[i]= ar[i][1];
    }
}

Svar #4

Föreställ dig en funktion för att fylla på tal med nollor n => n.padStart(8, "0") som tar vilket tal som helst och fyller på det, det vill säga

  • "19" -> "00000019"
  • "123" -> "00000123"

Denna funktion kan användas för att hjälpa till att sortera "19"-strängen så att den visas före "123"-strängen.

Låt oss lägga till ett regex /\d+/g som skapar den naturliga expansionsfunktionen str => str.replace(/\d+/g, n => n.padStart(8, "0")) som endast hittar numrerade delar i en sträng och fyller ut dem, dvs.

  • "19asd" -> "00000019asd"
  • "123asd" -> "00000123asd"

Nu kan vi använda denna naturliga expansionsfunktion för att hjälpa till att implementera naturlig ordningssortering:

const list = [
    "123asd",
    "19asd",
    "12345asd",
    "asd123",
    "asd12"
];

const ne = str => str.replace(/\d+/g, n => n.padStart(8, "0"));
const nc = (a,b) => ne(a).localeCompare(ne(b));

console.log(list.map(ne).sort()); // intermediate values
console.log(list.sort(nc)); // result

De mellansteg som visas av list.map(ne).sort() visar vad ne-funktionen för naturlig expansion gör. Den implementerar nollutfyllning av siffror enbart på siffervarianterna i strängen och lämnar alfabetkomponenterna oförändrade.

[
  "00000019asd",
  "00000123asd",
  "00012345asd",
  "asd00000012",
  "asd00000123"
]

Den slutliga versionen av lösningen implementerar en natural order-komparator nc implementerad som (a,b) => ne(a).localeCompare(ne(b)) och använder den i list.sort(nc) så att saker ordnas korrekt:

[
  "19asd",
  "123asd",
  "12345asd",
  "asd12",
  "asd123"
]

Relaterade artiklar