Javascript: tipo natural de sequências alfanuméricas

173

Eu estou procurando a maneira mais fácil de classificar uma matriz que consiste em números e texto e uma combinação deles.

Por exemplo

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

torna-se em

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

Isso será usado em combinação com a solução para outra pergunta que fiz aqui .

A função de classificação em si funciona, o que eu preciso é de uma função que possa dizer que '19asd' é menor que '123asd'.

Estou escrevendo isso em JavaScript.

Edit: como adormitu apontou, o que estou procurando é uma função para a classificação natural

ptrn
fonte
Veja também How do you do string comparison in JavaScript?em stackoverflow.com/questions/51165/…
Adrien Be
1
A pergunta original foi convidado em 2010, por isso não seria surpreendente :)
PTRN
Possível duplicata Como classificar seqüências em JavaScript
feeela

Respostas:

314

Agora isso é possível em navegadores modernos usando o localeCompare. Ao passar a numeric: trueopção, ele reconhecerá números de maneira inteligente. Você pode fazer distinção entre maiúsculas e minúsculas usando sensitivity: 'base'. Testado no Chrome, Firefox e IE11.

Aqui está um exemplo. Retorna1 , ou seja, 10 passa depois de 2:

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

Para obter desempenho ao classificar um grande número de cadeias, o artigo diz:

Ao comparar um grande número de seqüências de caracteres, como na classificação de matrizes grandes, é melhor criar um objeto Intl.Collator e usar a função fornecida por sua propriedade compare. Link do 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
fonte
12
Se você deseja classificar um array de objetos, você também pode usar o Collator: codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky
2
Para esclarecer o comentário acima: "Se o argumento das localidades não for fornecido ou for indefinido, a localidade padrão do tempo de execução será usada."
Gkely 28/07/19
46

Então você precisa de um tipo natural ?

Nesse caso, talvez esse script de Brian Huisman baseado no trabalho de David koelle seja o que você precisa.

Parece que a solução de Brian Huisman agora está diretamente hospedada no blog de David Koelle:

mhitza
fonte
O tipo correto e natural é o que estou procurando. Eu vou olhar para o link que você enviou, graças
PTRN
Esse é um tipo não natural. Não produz um tipo de anfíficos.
precisa saber é o seguinte
@ tchrist: o que você quer dizer com "não produz um tipo alfabético?"
Adrien Seja
Funciona bem, mas não lida com números negativos corretamente. Ou seja: produziria ['-1'. '-2', '0', '1', '2'].
adrianboimvaser
2
@mhitza este código parece fazer um bom trabalho github.com/litejs/natural-compare-lite veja um teste rápido jsbin.com/bevututodavi/1/edit?js,console
Adrien Seja
23

Para comparar valores, você pode usar um método de comparação

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

Mas, para agilizar a classificação de uma matriz, monte a matriz antes de classificá-la, para que você só precise fazer conversões em minúsculas e a expressão regular uma vez, em vez de em todas as etapas da classificação.

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
fonte
isso funcionaria no meu caso, com a matriz interna decidindo a ordem da matriz externa?
Ptrn
O que é String.prototype.tlc()? Esse é seu próprio código ou você o obteve de algum lugar? Se este for o caso, faça o link para a página.
Andy E
desculpe pelo erro corrigido, obrigado. Se você deseja que a [1] eb [1] controlem a classificação, use a = String (a [1]). ToLowerCase (); b = String (b [1]). toLowerCase ();
Kennebec
Eu tinha apenas uma lista de dados que queria classificar, e pensei que deveria ser fácil fazer isso no console do Chrome Dev Tools - obrigado pela função!
precisa saber é
9

Se você tiver uma matriz de objetos, poderá fazer o seguinte:

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

D0rm1nd0
fonte
1
Resposta perfeita! Obrigado.
hubert17 27/06
5

A biblioteca mais completa para lidar com isso a partir de 2019 parece ser de ordem natural .

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

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

const ordered = orderBy(unordered)

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

Ele não apenas recebe matrizes de cadeias, mas também pode classificar pelo valor de uma determinada chave em uma matriz de objetos. Também pode identificar e ordenar automaticamente cadeias de caracteres: moedas, datas, moeda e várias outras coisas.

Surpreendentemente, também é de apenas 1,6kB quando compactado.

Julien
fonte
2

Imagine uma função de preenchimento de 8 dígitos que transforma:

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

Podemos usar as cordas acolchoadas para nos ajudar a classificar '19asd' para aparecer antes de '123asd'.

Use a expressão regular /\d+/gpara ajudar a encontrar todos os números que precisam ser preenchidos:

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

A seguir demonstra a classificação usando 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

Os resultados intermediários mostram o que a rotina natural_expand () faz e fornece uma compreensão de como a rotina subsequente natural_compare funcionará:

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

Saídas:

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

Com base na resposta de @Adrien Be acima e usando o código que Brian Huisman e David koelle criaram, aqui está uma classificação de protótipo modificada para uma variedade 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
fonte