Javascript: tipo naturale di stringhe alfanumeriche


174

Sto cercando il modo più semplice per ordinare un array composto da numeri e testo e una combinazione di questi.

Per esempio

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

diventa

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

Questo verrà utilizzato in combinazione con la soluzione a un'altra domanda che ho posto qui .

La funzione di ordinamento in sé funziona, ciò di cui ho bisogno è una funzione che può dire che "19asd" è più piccolo di "123asd".

Sto scrivendo questo in JavaScript.

Modifica: come ha sottolineato Adormitu , quello che sto cercando è una funzione per l'ordinamento naturale


vedi anche How do you do string comparison in JavaScript?su stackoverflow.com/questions/51165/…
Adrien,

1
La domanda originale è stata posta nel 2010, quindi non sarebbe sorprendente :)
ptrn

Risposte:


318

Questo è ora possibile nei browser moderni utilizzando localeCompare. Passando l' numeric: trueopzione, riconoscerà in modo intelligente i numeri. Puoi fare distinzione tra maiuscole e minuscole usando sensitivity: 'base'. Testato su Chrome, Firefox e IE11.

Ecco un esempio Restituisce 1, ovvero 10 va dopo 2:

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

Per prestazioni durante l'ordinamento di un numero elevato di stringhe, l'articolo dice:

Quando si confrontano un numero elevato di stringhe, ad esempio nell'ordinamento di matrici di grandi dimensioni, è meglio creare un oggetto Intl.Collator e utilizzare la funzione fornita dalla sua proprietà compare. Collegamento a documenti

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


12
Se vuoi ordinare una matrice di oggetti, puoi anche usare Collator: codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky

2
Per chiarire il commento precedente: "Se l'argomento locales non viene fornito o non è definito, viene utilizzata la locale predefinita del runtime."
gkiely

46

Quindi hai bisogno di un ordinamento naturale ?

Se è così, forse questa sceneggiatura di Brian Huisman basata sul lavoro di David Koelle sarebbe ciò di cui hai bisogno.

Sembra che la soluzione di Brian Huisman sia ora direttamente ospitata sul blog di David Koelle:


L'ordinamento corretto e naturale è quello che sto cercando. Esaminerò il link che hai inviato, grazie
ptrn

È un tipo molto innaturale. Non produce un tipo alfetico.
tchrist

@tchrist: cosa intendi con "non produce un tipo alfabetico?"
Adrien,

Funziona bene ma non gestisce correttamente i numeri negativi. Vale a dire: produrrebbe ['-1'. '-2', '0', '1', '2'].
adrianboimvaser,

2
@mhitza questo codice sembra fare un buon lavoro github.com/litejs/natural-compare-lite vedere un test rapido jsbin.com/bevututodavi/1/edit?js,console
Adrien Be

23

Per confrontare i valori è possibile utilizzare un metodo di confronto

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;
}

Ma per velocizzare l'ordinamento di un array, attrezzare l'array prima dell'ordinamento, quindi è necessario eseguire solo conversioni minuscole e l'espressione regolare una volta anziché in ogni passaggio dell'ordinamento.

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];
    }
}

funzionerebbe nel mio caso, con l'array interno che decide l'ordine di quello esterno?
ptrn,

Cosa String.prototype.tlc()? È questo il tuo codice o l'hai preso da qualche parte? In quest'ultimo caso, si prega di collegarsi alla pagina.
Andy E

scusa per l'errore corretto, grazie. Se si desidera a [1] e b [1] per controllare l'ordinamento, utilizzare a = String (a [1]). ToLowerCase (); b = String (b [1]). toLowerCase ();
Kennebec,

Avevo appena un elenco di dati che volevo ordinare, ho pensato che dovrebbe essere facile da fare nella console di Chrome Dev Tools - grazie per la funzione!
ajh158,

9

Se hai una matrice di oggetti puoi fare così:

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


1
Risposta perfetta! Grazie.
hubert17

5

La libreria più completa per gestire questo a partire dal 2019 sembra essere naturale .

const { orderBy } = require('natural-orderby')

const unordered = [
  '123asd',
  '19asd',
  '12345asd',
  'asd123',
  'asd12'
]

const ordered = orderBy(unordered)

// [ '19asd',
//   '123asd',
//   '12345asd',
//   'asd12',
//   'asd123' ]

Non solo accetta matrici di stringhe, ma può anche ordinare in base al valore di una determinata chiave in una matrice di oggetti. Può anche identificare e ordinare automaticamente stringhe di: valute, date, valuta e un sacco di altre cose.

Sorprendentemente, è anche solo 1,6 kB quando compresso con zip.


2

Immagina una funzione di imbottitura a 8 cifre che trasforma:

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

Possiamo usare le stringhe imbottite per aiutarci a ordinare '19asd' affinché appaia prima di '123asd'.

Usa l'espressione regolare /\d+/gper trovare tutti i numeri che devono essere riempiti:

str.replace(/\d+/g, pad)

Di seguito viene illustrato l'ordinamento utilizzando questa tecnica:

var list = [
    '123asd',
    '19asd',
    '12345asd',
    'asd123',
    'asd12'
];

function pad(n) { return ("00000000" + n).substr(-8); }
function natural_expand(a) { return a.replace(/\d+/g, pad) };
function natural_compare(a, b) {
    return natural_expand(a).localeCompare(natural_expand(b));
}

console.log(list.map(natural_expand).sort()); // intermediate values
console.log(list.sort(natural_compare)); // result

I risultati intermedi mostrano cosa fa la routine natural_expand () e ti dà una comprensione di come funzionerà la successiva routine natural_compare:

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

Uscite:

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

1

Sulla base della risposta di @Adrien Be sopra e usando il codice creato da Brian Huisman e David Koelle , ecco un prototipo modificato per un array di oggetti:

//Usage: unsortedArrayOfObjects.alphaNumObjectSort("name");
//Test Case: var unsortedArrayOfObjects = [{name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a10"}, {name: "a5"}, {name: "a13"}, {name: "a20"}, {name: "a8"}, {name: "8b7uaf5q11"}];
//Sorted: [{name: "8b7uaf5q11"}, {name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a5"}, {name: "a8"}, {name: "a10"}, {name: "a13"}, {name: "a20"}]

// **Sorts in place**
Array.prototype.alphaNumObjectSort = function(attribute, caseInsensitive) {
  for (var z = 0, t; t = this[z]; z++) {
    this[z].sortArray = new Array();
    var x = 0, y = -1, n = 0, i, j;

    while (i = (j = t[attribute].charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        this[z].sortArray[++y] = "";
        n = m;
      }
      this[z].sortArray[y] += j;
    }
  }

  this.sort(function(a, b) {
    for (var x = 0, aa, bb; (aa = a.sortArray[x]) && (bb = b.sortArray[x]); x++) {
      if (caseInsensitive) {
        aa = aa.toLowerCase();
        bb = bb.toLowerCase();
      }
      if (aa !== bb) {
        var c = Number(aa), d = Number(bb);
        if (c == aa && d == bb) {
          return c - d;
        } else {
          return (aa > bb) ? 1 : -1;
        }
      }
    }

    return a.sortArray.length - b.sortArray.length;
  });

  for (var z = 0; z < this.length; z++) {
    // Here we're deleting the unused "sortArray" instead of joining the string parts
    delete this[z]["sortArray"];
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.