2013-03-18 14 views
34

Ho un array simile;Ordina elementi array (stringa con numeri), tipo naturale

["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"] 

E necessario ordinarlo in modo che venga visualizzato;

["IL0 Foo", "IL3 Bob says hello", "IL10 Baz", "PI0 Bar"] 

Ho provato una funzione di ordinamento;

function compare(a,b) { 
    if (a < b) 
    return -1; 
    if (a > b) 
    return 1; 
    return 0; 
} 

ma questo dà l'ordine

["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"] 

ho cercato di pensare a una regex che funziona ma non può ottenere la mia testa intorno ad esso.
Se aiuta il formato saranno sempre 2 lettere, x quantità di numeri, quindi un numero qualsiasi di caratteri.

+0

Lettera prima, quindi il numero? –

+0

@BradChristie, sì ordina per prime due lettere poi i numeri, gli altri caratteri non richiesti (ma sarebbe bello) – Rooneyl

+0

Quindi se tu avessi 'IL10 Ciao' e' IL10 Bob', che viene prima? –

risposta

71

Questo si chiama "sorta naturale" e può essere implementato in JS come questo:

function naturalCompare(a, b) { 
 
    var ax = [], bx = []; 
 

 
    a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) }); 
 
    b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) }); 
 
    
 
    while(ax.length && bx.length) { 
 
     var an = ax.shift(); 
 
     var bn = bx.shift(); 
 
     var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]); 
 
     if(nn) return nn; 
 
    } 
 

 
    return ax.length - bx.length; 
 
} 
 

 
///////////////////////// 
 

 
test = [ 
 
    "img12.png", 
 
    "img10.png", 
 
    "img2.png", 
 
    "img1.png", 
 
    "img101.png", 
 
    "img101a.png", 
 
    "abc10.jpg", 
 
    "abc10", 
 
    "abc2.jpg", 
 
    "20.jpg", 
 
    "20", 
 
    "abc", 
 
    "abc2", 
 
    "" 
 
]; 
 

 
test.sort(naturalCompare) 
 
document.write("<pre>" + JSON.stringify(test,0,3));

per ordinare in ordine inverso, basta scambiare gli argomenti:

test.sort(function(a, b) { return naturalCompare(b, a) }) 

o semplicemente

test = test.sort(naturalCompare).reverse(); 
+1

Sembra preferire stringhe che iniziano con caratteri alfabetici su caratteri numerici. Risolto rimpiazzando '$ 1 || 0' con 'typeof ($ 1) == 'undefined'? Infinity: $ 1' – colllin

+2

Qualcuno potrebbe aiutare come questo può essere implementato per l'ordinamento ascendente e discendente di array? – anusreemn

+0

@ user3513687: vedi aggiornamento – georg

0

Non bello, ma controllare i primi due codici char. Se tutto parse uguali e confronta i numeri:

var arr = ["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"]; 
arr.sort(function (a1, b1) { 
    var a = parseInt(a1.match(/\d+/g)[0], 10), 
     b = parseInt(b1.match(/\d+/g)[0], 10), 
     letterA = a1.charCodeAt(0), 
     letterB = b1.charCodeAt(0), 
     letterA1 = a1.charCodeAt(1), 
     letterB1 = b1.charCodeAt(1); 
    if (letterA > letterB) { 
     return 1; 
    } else if (letterB > letterA) { 
     return -1; 
    } else { 
     if (letterA1 > letterB1) { 
      return 1; 
     } else if (letterB1 > letterA1) { 
      return -1; 
     } 
     if (a < b) return -1; 
     if (a > b) return 1; 
     return 0; 
    } 
}); 

Example

+0

che mi dà l'ordine ["IL0 Foo", "IL10 Baz", "IL3 Bob dice ciao", "PI0 Bar"]. IL3 dovrebbe essere il secondo elemento – Rooneyl

+0

@Rooneyl, sì, ho bisogno di aggiungere un + alla regex. Ho aggiornato – Joe

5
var re = /([a-z]+)(\d+)(.+)/i; 
var arr = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"]; 
var order = arr.sort(function(a,b){ 
    var ma = a.match(re), 
     mb = b.match(re), 
     a_str = ma[1], 
     b_str = mb[1], 
     a_num = parseInt(ma[2],10), 
     b_num = parseInt(mb[2],10), 
     a_rem = ma[3], 
     b_rem = mb[3]; 
    return a_str > b_str ? 1 : a_str < b_str ? -1 : a_num > b_num ? 1 : a_num < b_num ? -1 : a_rem > b_rem; 
}); 
1

Si potrebbe fare una regex come questa per ottenere le parti non numerici e numerici della stringa:

var s = "foo124bar23"; 
s.match(/[^\d]+|\d+/g) 

restituisce: ["foo", "124" , "bar" , "23"]

Quindi nella funzione di confronto è possibile iterare th sgrossare le parti delle due stringhe confrontandole part-by-part. La prima parte non corrispondente determina il risultato del confronto complessivo. Per ogni parte, controllare se la parte inizia con una cifra e in tal caso analizzarla come numero prima di eseguire il confronto.

1

aggiungere un altro alternativa (perché no):

var ary = ["IL0 Foo", "PI0 Bar", "IL10 Hello", "IL10 Baz", "IL3 Bob says hello"]; 

// break out the three components in to an array 
// "IL10 Bar" => ['IL', 10, 'Bar'] 
function getParts(i){ 
    i = i || ''; 
    var parts = i.match(/^([a-z]+)([0-9]+)(\s.*)$/i); 
    if (parts){ 
     return [ 
      parts[1], 
      parseInt(parts[2], 10), 
      parts[3] 
     ]; 
    } 
    return []; // erroneous 
} 
ary.sort(function(a,b){ 
    // grab the parts 
    var _a = getParts(a), 
     _b = getParts(b); 

    // trouble parsing (both fail = no shift, otherwise 
    // move the troubles element to end of the array) 
    if(_a.length == 0 && _b.length == 0) return 0; 
    if(_a.length == 0) return -1; 
    if(_b.length == 0) return 1; 

    // Compare letter portion 
    if (_a[0] < _b[0]) return -1; 
    if (_a[0] > _b[0]) return 1; 
    // letters are equal, continue... 

    // compare number portion 
    if (_a[1] < _b[1]) return -1; 
    if (_a[1] > _b[1]) return 1; 
    // numbers are equal, continue... 

    // compare remaining string 
    if (_a[2] < _b[2]) return -1; 
    if (_a[2] > _b[2]) return 1; 
    // strings are equal, continue... 

    // exact match 
    return 0; 
}); 

jsfiddle example

3

mi è piaciuta la soluzione di Georg molto, ma avevo bisogno di sottolineatura ("_") per ordinare prima dei numeri. Ecco come ho modificato il suo codice:

var chunkRgx = /(_+)|([0-9]+)|([^0-9_]+)/g; 
 
function naturalCompare(a, b) { 
 
    var ax = [], bx = []; 
 
    
 
    a.replace(chunkRgx, function(_, $1, $2, $3) { 
 
     ax.push([$1 || "0", $2 || Infinity, $3 || ""]) 
 
    }); 
 
    b.replace(chunkRgx, function(_, $1, $2, $3) { 
 
     bx.push([$1 || "0", $2 || Infinity, $3 || ""]) 
 
    }); 
 
    
 
    while(ax.length && bx.length) { 
 
     var an = ax.shift(); 
 
     var bn = bx.shift(); 
 
     var nn = an[0].localeCompare(bn[0]) || 
 
       (an[1] - bn[1]) || 
 
       an[2].localeCompare(bn[2]); 
 
     if(nn) return nn; 
 
    } 
 
    
 
    return ax.length - bx.length; 
 
} 
 

 
///////////////////////// 
 

 
test = [ 
 
    "img12.png", 
 
    "img10.png", 
 
    "img2.png", 
 
    "img1.png", 
 
    "img101.png", 
 
    "img101a.png", 
 
    "abc10.jpg", 
 
    "abc10", 
 
    "abc2.jpg", 
 
    "20.jpg", 
 
    "20", 
 
    "abc", 
 
    "abc2", 
 
    "_abc", 
 
    "_ab_c", 
 
    "_ab__c", 
 
    "_abc_d", 
 
    "ab_", 
 
    "abc_", 
 
    "_ab_cd", 
 
    "" 
 
]; 
 

 
test.sort(naturalCompare) 
 
document.write("<pre>" + JSON.stringify(test,0,3));

3

numeri Pad nella stringa con gli zeri, quindi ordinare normalmente.

var naturalSort = function (a, b) { 
 
    a = ('' + a).replace(/(\d+)/g, function (n) { return ('0000' + n).slice(-5) }); 
 
    b = ('' + b).replace(/(\d+)/g, function (n) { return ('0000' + n).slice(-5) }); 
 
    return a.localeCompare(b); 
 
} 
 

 
var naturalSortModern = function (a, b) { 
 
    return ('' + a).localeCompare(('' + b), 'en', { numeric: true }); 
 
} 
 

 
console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSort))); 
 

 
console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSortModern)));

2

Si potrebbe usare con options

sensibilità

Quali differenze nelle stringhe dovrebbero portare ad valori dei risultati diversi da zero. I valori possibili sono:

  • "base": solo le stringhe che differiscono nelle lettere di base vengono confrontate come non uguali. Esempi: a ≠ b, a = á, a = A.
  • "accent": solo le stringhe che differiscono in lettere di base o accenti e altri segni diacritici si confrontano come non uguali. Esempi: a ≠ b, a ≠ á, a = A.
  • "case": solo le stringhe che differiscono nelle lettere di base o nel caso si confrontano come non uguali. Esempi: a ≠ b, a = á, a ≠ A.
  • "variant": stringhe che differiscono in lettere di base, accenti e altri segni diacritici, o caso di confronto come non uguale. Altre differenze possono anche essere prese in considerazione. Esempi: a ≠ b, a ≠ á, a ≠ A.

L'impostazione predefinita è "variante" per l'utilizzo "ordinamento"; dipende dalle impostazioni locali per l'utilizzo di "ricerca".

numerico

Sia confronto numerico deve essere utilizzato, tale che "1" < "2" < "10". I valori possibili sono true e false; il valore predefinito è false. Questa opzione può essere impostata tramite una proprietà di opzioni o tramite una chiave di estensione Unicode; se entrambi sono forniti, la proprietà options ha la precedenza. Non sono richieste implementazioni per supportare questa proprietà.

var array = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"]; 
 

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

 
console.log(array);