Javascript: tipo natural de cadenas alfanuméricas

174

Estoy buscando la forma más fácil de ordenar una matriz que consta de números y texto, y una combinación de estos.

P.ej

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

se convierte en

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

Esto se usará en combinación con la solución a otra pregunta que he hecho aquí .

La función de clasificación en sí misma funciona, lo que necesito es una función que pueda decir que '19asd' es más pequeño que '123asd'.

Estoy escribiendo esto en JavaScript.

Editar: como señaló adormitu , lo que estoy buscando es una función para la clasificación natural

ptrn
fuente
ver también How do you do string comparison in JavaScript?en stackoverflow.com/questions/51165/…
Adrien Be
1
La pregunta original se hizo en 2010, por lo que no sería sorprendente :)
ptrn
Posible duplicado de Cómo ordenar cadenas en JavaScript
feeela

Respuestas:

318

Esto ahora es posible en los navegadores modernos que usan localeCompare. Al pasar la numeric: trueopción, reconocerá inteligentemente los números. Puede hacer mayúsculas y minúsculas usando sensitivity: 'base'. Probado en Chrome, Firefox e IE11.

Aquí hay un ejemplo. Regresa 1, lo que significa que 10 va después de 2:

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

Para el rendimiento al ordenar grandes cantidades de cadenas, el artículo dice:

Al comparar grandes cantidades de cadenas, como al ordenar grandes matrices, es mejor crear un objeto Intl.Collator y usar la función proporcionada por su propiedad de comparación. Enlace de documentos

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

frodo2975
fuente
12
Si desea ordenar un conjunto de objetos, también puede usar el Collator: codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky
2
Para aclarar el comentario anterior: "Si el argumento de las configuraciones regionales no se proporciona o no está definido, se usa la configuración regional predeterminada del tiempo de ejecución".
gkiely
46

Entonces, ¿necesitas un tipo natural ?

Si es así, entonces quizás este guión de Brian Huisman basado en el trabajo de David Koelle sería lo que necesita.

Parece que la solución de Brian Huisman ahora está alojada directamente en el blog de David Koelle:

mhitza
fuente
El tipo correcto y natural es lo que estoy buscando.
Buscaré
Ese es un tipo muy poco natural. No produce un tipo alfabético.
tchrist
@tchrist: ¿qué quieres decir con "no produce un tipo alfabético?"
Adrien Be
Funciona bien pero no maneja los números negativos correctamente. Es decir: produciría ['-1'. '-2', '0', '1', '2'].
adrianboimvaser
2
@mhitza este código parece hacer un buen trabajo github.com/litejs/natural-compare-lite vea una prueba rápida jsbin.com/bevututodavi/1/edit?js,console
Adrien Be
23

Para comparar valores, puede usar un método de comparación:

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

Pero para acelerar la ordenación de una matriz, manipule la matriz antes de ordenarla, de modo que solo tenga que hacer conversiones en minúsculas y la expresión regular una vez en lugar de en cada paso de la ordenación.

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];
    }
}
Kennebec
fuente
¿funcionaría esto en mi caso, con la matriz interna decidiendo el orden de la externa?
ptrn
¿Qué es String.prototype.tlc()? ¿Es este tu propio código o lo obtuviste de alguna parte? Si es esto último, por favor enlace a la página.
Andy E
perdón por el error corregido, gracias. Si desea que a [1] yb [1] controlen la ordenación, use a = String (a [1]). ToLowerCase (); b = Cadena (b [1]). toLowerCase ();
Kennebec
Acabo de tener una lista de datos que quería ordenar, pensé que debería ser fácil de hacer en la consola de Chrome Dev Tools. ¡Gracias por la función!
ajh158
9

Si tiene una variedad de objetos, puede hacer esto:

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

D0rm1nd0
fuente
1
¡Respuesta perfecta! Gracias.
hubert17
5

La biblioteca más completa para manejar esto a partir de 2019 parece ser de orden natural .

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

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

const ordered = orderBy(unordered)

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

No solo toma matrices de cadenas, sino que también puede ordenar por el valor de una determinada clave en una matriz de objetos. También puede identificar y ordenar automáticamente cadenas de: monedas, fechas, moneda y muchas otras cosas.

Sorprendentemente, también es solo 1.6kB cuando se comprime.

Julien
fuente
2

Imagine una función de relleno de 8 dígitos que transforma:

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

Podemos utilizar las cadenas acolchadas para ayudarnos a clasificar '19asd' para que aparezca antes de '123asd'.

Use la expresión regular /\d+/gpara ayudar a encontrar todos los números que deben rellenarse:

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

A continuación se muestra la clasificación mediante esta técnica:

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

Los resultados intermedios muestran lo que hace la rutina natural_expand () y le da una idea de cómo funcionará la rutina natural_compare posterior:

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

Salidas:

[
  "19asd",
  "123asd",
  "12345asd",
  "asd12",
  "asd123"
]
Stephen Quan
fuente
1

Sobre la base de la respuesta de @Adrien Be anterior y utilizando el código que crearon Brian Huisman y David Koelle , aquí hay un prototipo modificado para una variedad de objetos:

//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"];
  }
}
Eric Norcross
fuente