Como classificar strings em JavaScript

344

Eu tenho uma lista de objetos que desejo classificar com base em um campo attrdo tipo string. Eu tentei usar-

list.sort(function (a, b) {
    return a.attr - b.attr
})

mas descobriu que -não parece funcionar com cadeias de caracteres em JavaScript. Como posso classificar uma lista de objetos com base em um atributo com o tipo string?

airportyh
fonte
11
Veja JavaScript case insensitive string comparisonem stackoverflow.com/questions/2140627/…
Adrien Seja
Para uma solução rápida "internacionalizada" (apenas em parte, acho que isso pode não abranger todos os sotaques do mundo), você pode simplesmente ignorar os sotaques, ou seja, removê-los. Depois, faça apenas sua comparação de cadeias, veja Javascript : remove accents/diacritics in stringsem stackoverflow.com/questions/990904/…
Adrien Seja
2
Engraçado o suficiente Jeff Atwood próprio escreveu um post sobre isso de volta problema comum em 2007, ver blog.codinghorror.com/sorting-for-humans-natural-sort-order
Adrien Seja

Respostas:

620

Use String.prototype.localeCompareum por seu exemplo:

list.sort(function (a, b) {
    return ('' + a.attr).localeCompare(b.attr);
})

Forçamos a.attr a ser uma string para evitar exceções. localeCompareé suportado desde o Internet Explorer 6 e Firefox 1. Você também pode ver o seguinte código usado que não respeita um código de idioma:

if (item1.attr < item2.attr)
  return -1;
if ( item1.attr > item2.attr)
  return 1;
return 0;
Shog9
fonte
81
Antes que alguém cometa o mesmo erro precipitado que eu, é local e Compare, não localCompare.
9/09/12
12
A primeira solução considerará "A" depois de "z", mas antes de "Z", pois ele faz uma comparação no valor ASCII do caractere. localeCompare()não se depara com esse problema, mas não entende os valores numéricos, então você obterá ["1", "10", "2"] como nas comparações de classificação na maioria dos idiomas. se você quiser classificar para sua extremidade dianteira UI, olhar para o alphanum / classificação natural algoritmo stackoverflow.com/questions/4340227/... ou stackoverflow.com/questions/4321829/...
Dead.Rabit
2
Observe que isso localeCompare()é suportado apenas em navegadores modernos: IE11 + no momento da redação deste artigo, consulte developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Adrien Seja
3
Não, quero dizer a primeira linha da tabela, o @Adrien - IE suporta localeCompare()várias versões anteriores, mas não especifica a localidade até a versão 11. Observe também as perguntas às quais o Dead.Rabit se vinculou.
precisa saber é
3
@ Shog9 meu mal, parece que é suportado desde o IE6! consulte (role para baixo / pesquise o método localeCompare ()) em msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx . No entanto, nas implementações antigas em que não usamos os argumentos locales e opções (o usado antes do IE11), a localidade e a ordem de classificação usadas são totalmente dependentes da implementação , em outras palavras: Firefox, Safari, Chrome e IE NÃO classifique as strings na mesma ordem. consulte code.google.com/p/v8/issues/detail?id=459
Adrien Seja
166

Uma resposta atualizada (outubro de 2014)

Fiquei realmente irritado com essa ordem de classificação natural da string, por isso levei algum tempo para investigar esse problema. Eu espero que isso ajude.

Longa história curta

localeCompare()suporte a personagens é foda, basta usá-lo. Conforme indicado por Shog9, a resposta para sua pergunta é:

return item1.attr.localeCompare(item2.attr);

Erros encontrados em todas as implementações personalizadas de "ordem de classificação de sequência natural" em javascript

Existem várias implementações personalizadas por aí, tentando fazer uma comparação de strings mais precisamente chamada "ordem natural de classificação de strings"

Ao "brincar" com essas implementações, sempre notei alguma escolha estranha de "ordem de classificação natural", ou melhor, erros (ou omissões nos melhores casos).

Normalmente, caracteres especiais (espaço, traço, e comercial, colchetes e assim por diante) não são processados ​​corretamente.

Você os encontrará aparecendo misturados em lugares diferentes, normalmente que podem ser:

  • alguns estarão entre maiúsculas 'Z' e minúsculas 'a'
  • alguns estarão entre o '9' e o 'A' maiúsculo
  • alguns serão depois de 'z' minúsculo

Quando seria de esperar que todos os caracteres especiais fossem "agrupados" em um só lugar, exceto o caractere especial de espaço talvez (que sempre seria o primeiro caractere). Ou seja, tudo antes dos números ou entre números e letras (letras minúsculas e maiúsculas estão "juntas" uma após a outra) ou todas as letras.

Minha conclusão é que todos eles falham em fornecer uma ordem consistente quando começo a adicionar caracteres pouco comuns (ou seja, caracteres com sinais diacríticos ou caracteres como traço, ponto de exclamação e assim por diante).

Pesquisa sobre implementações personalizadas:

Implementações nativas da "ordem de classificação de cadeia natural" dos navegadores via localeCompare()

localeCompare()A implementação mais antiga (sem os argumentos códigos de idioma e opções) é suportada pelo IE6 +, consulte http://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (role para baixo até localeCompare ( )). O localeCompare()método incorporado faz um trabalho muito melhor na classificação, mesmo em caracteres internacionais e especiais. O único problema usando o localeCompare()método é que "a localidade e a ordem de classificação usadas são totalmente dependentes da implementação". Em outras palavras, ao usar o localeCompare, como stringOne.localeCompare (stringTwo): Firefox, Safari, Chrome e IE têm uma ordem de classificação diferente para Strings.

Pesquisa sobre implementações nativas do navegador:

Dificuldade de "ordem de classificação natural das cadeias"

A implementação de um algoritmo sólido (significando: consistente, mas também cobrindo uma ampla variedade de caracteres) é uma tarefa muito difícil. O UTF8 contém mais de 2000 caracteres e abrange mais de 120 scripts (idiomas) . Finalmente, existem algumas especificações para essas tarefas, chamadas de "Algoritmos de Collation Unicode", que podem ser encontrados em http://www.unicode.org/reports/tr10/ . Você pode encontrar mais informações sobre isso nesta questão que eu publiquei /software/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order

Conclusão final

Portanto, considerando o nível atual de suporte fornecido pelas implementações personalizadas de javascript que encontrei, provavelmente nunca veremos nada próximo de suportar todos esses caracteres e scripts (idiomas). Por isso, prefiro usar o método localeCompare () nativo dos navegadores. Sim, ele tem a desvantagem de não ser consistente entre os navegadores, mas os testes básicos mostram que ele abrange uma variedade muito maior de caracteres, permitindo ordens de classificação sólidas e significativas.

Portanto, conforme indicado por Shog9, a resposta para sua pergunta é:

return item1.attr.localeCompare(item2.attr);

Leitura adicional:

Graças à boa resposta de Shog9, que me colocou na direção "certa", acredito

Adrien Be
fonte
38

Resposta (no ECMAScript moderno)

list.sort((a, b) => (a.attr > b.attr) - (a.attr < b.attr))

Ou

list.sort((a, b) => +(a.attr > b.attr) || -(a.attr < b.attr))

Descrição

A conversão de um valor booleano em um número produz o seguinte:

  • true -> 1
  • false -> 0

Considere três padrões possíveis:

  • x é maior que y: (x > y) - (y < x)-> 1 - 0->1
  • x é igual a y: (x > y) - (y < x)-> 0 - 0->0
  • x é menor que y: (x > y) - (y < x)-> 0 - 1->-1

(Alternativa)

  • x é maior que y: +(x > y) || -(x < y)-> 1 || 0->1
  • x é igual a y: +(x > y) || -(x < y)-> 0 || 0->0
  • x é menor que y: +(x > y) || -(x < y)-> 0 || -1->-1

Portanto, essas lógicas são equivalentes às funções típicas do comparador de classificação.

if (x == y) {
    return 0;
}
return x > y ? 1 : -1;
mpyw
fonte
11
Como eu comentei a resposta anterior que usou esse truque, as respostas somente de código podem ser mais úteis, explicando como elas funcionam.
Dan Dascalescu 30/10
Descrição adicionada
mpyw 4/11/2018
Você pode comentar se isso é melhor ou pior que o localeCompare?
Ran Lottem 13/01/19
3
O @RanLottem localeComparee a comparação padrão produzem resultados diferentes. Qual você espera? ["A", "b", "C", "d"].sort((a, b) => a.localeCompare(b))classifica em ordem alfabética case-insensitive, enquanto ["A", "b", "C", "d"].sort((a, b) => (a > b) - (a < b))faz a fim codepoint
mpyw
Entendo, isso parece ser o principal ponto de discórdia. Alguma idéia sobre diferenças de desempenho?
Ran Lottem
13

Você deve usar> ou <e == aqui. Portanto, a solução seria:

list.sort(function(item1, item2) {
    var val1 = item1.attr,
        val2 = item2.attr;
    if (val1 == val2) return 0;
    if (val1 > val2) return 1;
    if (val1 < val2) return -1;
});
airportyh
fonte
11
Em uma nota lateral, isso não manipulará comparações de string versus número. Por exemplo: 'Z' <9 (falso), 'Z'> 9 (também falso ??), 'Z' == 9 (também falso !!). Parva NaN em JavaScript ...
Kato
7

Função de seta ternária aninhada

(a,b) => (a < b ? -1 : a > b ? 1 : 0)
lagartixas
fonte
7

como as strings podem ser comparadas diretamente em javascript, isso fará o trabalho

list.sort(function (a, b) {
    return a.attr > b.attr ? 1: -1;
})

a subtração em uma função de classificação é usada apenas quando a classificação não alfabética (numérica) é desejada e, é claro, não funciona com strings

Alejadro Xalabarder
fonte
6

Eu me preocupei com isso por muito tempo, então finalmente pesquisei sobre isso e explico por que as coisas são do jeito que são.

Das especificações :

Section 11.9.4   The Strict Equals Operator ( === )

The production EqualityExpression : EqualityExpression === RelationalExpression
is evaluated as follows: 
- Let lref be the result of evaluating EqualityExpression.
- Let lval be GetValue(lref).
- Let rref be the result of evaluating RelationalExpression.
- Let rval be GetValue(rref).
- Return the result of performing the strict equality comparison 
  rval === lval. (See 11.9.6)

Então agora vamos para 11.9.6

11.9.6   The Strict Equality Comparison Algorithm

The comparison x === y, where x and y are values, produces true or false. 
Such a comparison is performed as follows: 
- If Type(x) is different from Type(y), return false.
- If Type(x) is Undefined, return true.
- If Type(x) is Null, return true.
- If Type(x) is Number, then
...
- If Type(x) is String, then return true if x and y are exactly the 
  same sequence of characters (same length and same characters in 
  corresponding positions); otherwise, return false.

É isso aí. O operador triple equals aplicado às strings retorna true se os argumentos são exatamente as mesmas strings (mesmo comprimento e mesmos caracteres nas posições correspondentes).

Portanto ===, funcionará nos casos em que estivermos tentando comparar cadeias que podem ter chegado de fontes diferentes, mas que sabemos que acabarão tendo os mesmos valores - um cenário bastante comum para cadeias de caracteres embutidas em nosso código. Por exemplo, se tivermos uma variável denominada connection_statee desejamos saber em qual dos seguintes estados ['connecting', 'connected', 'disconnecting', 'disconnected']ela está agora, podemos usar diretamente o ===.

Mas tem mais. Logo acima do 11.9.4, há uma breve nota:

NOTE 4     
  Comparison of Strings uses a simple equality test on sequences of code 
  unit values. There is no attempt to use the more complex, semantically oriented
  definitions of character or string equality and collating order defined in the 
  Unicode specification. Therefore Strings values that are canonically equal
  according to the Unicode standard could test as unequal. In effect this 
  algorithm assumes that both Strings are already in normalized form.

Hmm. E agora? Cordas obtidas externamente podem, e muito provavelmente serão, unicodey estranho, e nosso gentil ===não fará justiça a elas. Em vem localeCompareao salvamento:

15.5.4.9   String.prototype.localeCompare (that)
    ...
    The actual return values are implementation-defined to permit implementers 
    to encode additional information in the value, but the function is required 
    to define a total ordering on all Strings and to return 0 when comparing
    Strings that are considered canonically equivalent by the Unicode standard. 

Podemos ir para casa agora.

tl; dr;

Para comparar strings em javascript, use localeCompare; se você souber que as seqüências não têm componentes não ASCII porque são, por exemplo, constantes internas do programa, ===também funciona.

Manav
fonte
0

Na sua operação na sua pergunta inicial, você está executando a seguinte operação:

item1.attr - item2.attr

Portanto, supondo que sejam números (por exemplo, item1.attr = "1", item2.attr = "2") Você ainda pode usar o operador "===" (ou outros avaliadores rigorosos), desde que garanta o tipo. O seguinte deve funcionar:

return parseInt(item1.attr) - parseInt(item2.attr);

Se eles são alphaNumeric, use localCompare ().

eggmatters
fonte
0
list.sort(function(item1, item2){
    return +(item1.attr > item2.attr) || +(item1.attr === item2.attr) - 1;
}) 

Como eles funcionam amostras:

+('aaa'>'bbb')||+('aaa'==='bbb')-1
+(false)||+(false)-1
0||0-1
-1

+('bbb'>'aaa')||+('bbb'==='aaa')-1
+(true)||+(false)-1
1||0-1
1

+('aaa'>'aaa')||+('aaa'==='aaa')-1
+(false)||+(true)-1
0||1-1
0
Petr Varyagin
fonte
3
As respostas somente de código podem ser mais úteis, explicando como elas funcionam.
Dan Dascalescu
-2
<!doctype html>
<html>
<body>
<p id = "myString">zyxtspqnmdba</p>
<p id = "orderedString"></p>
<script>
var myString = document.getElementById("myString").innerHTML;
orderString(myString);
function orderString(str) {
    var i = 0;
    var myArray = str.split("");
    while (i < str.length){
        var j = i + 1;
        while (j < str.length) {
            if (myArray[j] < myArray[i]){
                var temp = myArray[i];
                myArray[i] = myArray[j];
                myArray[j] = temp;
            }
            j++;
        }
        i++;
    }
    var newString = myArray.join("");
    document.getElementById("orderedString").innerHTML = newString;
}
</script>
</body>
</html>
Julio Munoz
fonte
11
Adicione algumas informações sobre como isso resolverá a pergunta na sua resposta. Respostas somente de código não são bem-vindas. Obrigado.
wayneOS
aqui você deseja ordenar os caracteres dentro de uma string, que não é o que é solicitado. Você pode conseguir isto triagem simplesmente usando "Array.sort" eg str.split ( "") sort () .join. ( "")
Alejadro Xalabarder
-2
var str = ['v','a','da','c','k','l']
var b = str.join('').split('').sort().reverse().join('')
console.log(b)
Abdul
fonte
Embora esse código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade da sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo à pergunta dos leitores no futuro, não apenas à pessoa que está perguntando agora. Por favor edite sua resposta para adicionar explicação, e dar uma indicação do que limitações e premissas se aplicam.
Dave