Ordenar elementos de matriz (cadena con números), ordenación natural

Resuelto Rooneyl asked hace 11 años • 8 respuestas

Tengo una matriz como;

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

Y es necesario ordenarlo para que parezca;

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

Probé una función de clasificación;

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

pero esto da la orden

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

He tratado de pensar en una expresión regular que funcione, pero no puedo entenderla.
Si ayuda, el formato siempre será de 2 letras, x cantidad de números y luego cualquier cantidad de caracteres.

Rooneyl avatar Mar 18 '13 21:03 Rooneyl
Aceptado

Esto se llama "clasificación natural" y se puede implementar en JS de esta manera:

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));
Expandir fragmento

Para ordenar en orden inverso, simplemente intercambie los argumentos:

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

o simplemente

test = test.sort(naturalCompare).reverse();
georg avatar Mar 18 '2013 14:03 georg

Podrías usar String#localeCompareconoptions

sensibilidad

Qué diferencias en las cadenas deberían conducir a valores de resultados distintos de cero. Los valores posibles son:

  • "base": Sólo las cadenas que difieren en letras base se comparan como desiguales. Ejemplos: a ≠ b, a = á, a = A.
  • "accent": Sólo las cadenas que difieren en letras base o acentos y otros signos diacríticos se comparan como desiguales. Ejemplos: a ≠ b, a ≠ á, a = A.
  • "case": Sólo las cadenas que difieren en letras base o mayúsculas y minúsculas se comparan como desiguales. Ejemplos: a ≠ b, a = á, a ≠ A.
  • "variant": cadenas que difieren en letras base, acentos y otros signos diacríticos, o comparación de mayúsculas y minúsculas como desiguales. También se pueden tener en cuenta otras diferencias. Ejemplos: a ≠ b, a ≠ á, a ≠ A.

El valor predeterminado es "variante" para el uso "ordenar"; Depende de la configuración regional para el uso de "búsqueda".

numérico

Si se debe utilizar la intercalación numérica, de modo que "1" < "2" < "10". Los valores posibles son truey false; el valor predeterminado es false. Esta opción se puede configurar mediante una propiedad de opciones o mediante una clave de extensión Unicode; si se proporcionan ambos, la optionspropiedad tiene prioridad. No se requieren implementaciones para respaldar esta propiedad.

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);
Expandir fragmento

Nina Scholz avatar Jul 24 '2017 11:07 Nina Scholz
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;  
});
epascarello avatar Mar 18 '2013 14:03 epascarello

Rellene los números en la cadena con ceros a la izquierda y luego ordénelos 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)));
Expandir fragmento

axfree avatar May 08 '2017 06:05 axfree

Me gustó mucho la solución de Georg, pero necesitaba guiones bajos ("_") para ordenar antes de los números. Así es como modifiqué su código:

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));
Expandir fragmento

Chaim Leib Halbert avatar Jul 24 '2015 19:07 Chaim Leib Halbert