Como classificar strings em JavaScript
-
09-06-2019 - |
Pergunta
Tenho uma lista de objetos que desejo classificar com base em um campo attr
do tipo string.Eu tentei usar -
list.sort(function (a, b) {
return a.attr - b.attr
})
mas descobri que -
parece não funcionar com strings em JavaScript.Como posso classificar uma lista de objetos com base em um atributo do tipo string?
Solução
Usar String.prototype.localeCompare
a pelo 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
foi apoiado desde o Internet Explorer 6 e Firefox 1.Você também pode ver o seguinte código usado que não respeita uma localidade:
if (item1.attr < item2.attr)
return -1;
if ( item1.attr > item2.attr)
return 1;
return 0;
Outras dicas
Uma resposta atualizada (outubro de 2014)
Fiquei muito irritado com essa ordem de classificação natural das strings, então levei algum tempo para investigar esse problema.Eu espero que isso ajude.
Longa história curta
localeCompare()
suporte de personagem é foda, basta usá-lo.Como apontado por Shog9
, a resposta para sua pergunta é:
return item1.attr.localeCompare(item2.attr);
Bugs encontrados em todas as implementações de "ordem de classificação de string natural" de javascript personalizado
Existem várias implementações personalizadas por aí, tentando fazer comparação de strings com mais precisão, chamadas de "ordem de classificação natural 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 na melhor das hipóteses).
Normalmente, os 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:
- alguns estarão entre 'Z' maiúsculo e 'a' minúsculo
- alguns estarão entre o '9' e o 'A' maiúsculo
- alguns estarão depois de 'z' minúsculo
Quando seria de se esperar que todos os caracteres especiais fossem "agrupados" em um só lugar, exceto talvez o caractere especial de espaço (que seria sempre o primeiro caractere).Ou seja, tudo antes dos números, ou tudo entre números e letras (minúsculas e maiúsculas sendo "juntas" uma após a outra), ou tudo depois das letras.
Minha conclusão é que todos eles falham em fornecer uma ordem consistente quando começo a adicionar caracteres pouco incomuns (ou seja,caracteres com diacríticos ou caracteres como traço, ponto de exclamação e assim por diante).
Pesquisa sobre as implementações personalizadas:
Natural Compare Lite
https://github.com/litejs/natural-compare-lite :Falha na classificação consistente https://github.com/litejs/natural-compare-lite/issues/1 e http://jsbin.com/bevututodavi/1/edit?js,console , classificação básica de caracteres latinos http://jsbin.com/bevututodavi/5/edit?js,consoleNatural Sort
https://github.com/javve/natural-sort :Falha na classificação consistente, consulte o problema https://github.com/javve/natural-sort/issues/7 e veja a classificação básica de caracteres latinos http://jsbin.com/cipimosedoqe/3/edit?js,consoleJavascript Natural Sort
https://github.com/overset/javascript-natural-sort :parece bastante negligenciado desde fevereiro de 2012. Falha na classificação consistente, consulte o problema https://github.com/overset/javascript-natural-sort/issues/16Alphanum
http://www.davekoelle.com/files/alphanum.js , Falha na classificação consistente, consulte http://jsbin.com/tuminoxifuyo/1/edit?js,console
Implementações nativas de "ordem de classificação de string natural" dos navegadores via localeCompare()
localeCompare()
a implementação mais antiga (sem os argumentos locales e options) é suportada pelo IE6+, consulte http://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (role para baixo até o método localeCompare()).O embutido localeCompare()
O método faz um trabalho muito melhor na classificação, até mesmo de caracteres internacionais e especiais.O único problema ao usar o localeCompare()
método é esse "a localidade e a ordem de classificação usadas dependem inteiramente da implementação".Em outras palavras, ao usar localeCompare como stringOne.localeCompare(stringTwo):Firefox, Safari, Chrome e IE têm uma ordem de classificação diferente para Strings.
Pesquisa sobre as implementações nativas do navegador:
- http://jsbin.com/beboroyifomu/1/edit?js,console - comparação básica de caracteres latinos com localeCompare()http://jsbin.com/viyucavudela/2/ - comparação básica de caracteres latinos com localeCompare() para teste no IE8
- http://jsbin.com/beboroyifomu/2/edit?js,console - caracteres latinos básicos na comparação de strings:verificação de consistência na string vs quando um personagem está sozinho
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare - IE11+ suporta os novos argumentos de localidades e opções
Dificuldade de "ordem de classificação natural de strings"
Implementando um algoritmo sólido (ou seja:consistente, mas também cobrindo uma ampla gama de personagens) é uma tarefa muito difícil.UTF8 contém mais de 2.000 caracteres & abrange mais de 120 scripts (idiomas).Por fim, existem algumas especificações para esta tarefa, é chamada de "Algoritmo de agrupamento Unicode", que pode ser encontrada em http://www.unicode.org/reports/tr10/ .Você pode encontrar mais informações sobre isso nesta pergunta que postei https://softwareengineering.stackexchange.com/questions/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 chegando perto de oferecer suporte a todos esses caracteres e scripts (linguagens).Portanto, prefiro usar o método localeCompare() nativo dos navegadores.Sim, tem a desvantagem de não ser consistente entre navegadores, mas os testes básicos mostram que cobre uma gama muito mais ampla de caracteres, permitindo ordens de classificação sólidas e significativas.
Então como apontado por Shog9
, a resposta para sua pergunta é:
return item1.attr.localeCompare(item2.attr);
Leitura adicional:
- https://softwareengineering.stackexchange.com/questions/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order
- Como você faz comparação de strings em JavaScript?
- JavaScript:tipo natural de strings alfanuméricas
- Classificar matriz de elementos numéricos e alfabéticos (classificação natural)
- Classificar matriz alfa/numérica mista
- https://web.archive.org/web/20130929122019/http://my.opera.com/GreyWyvern/blog/show.dml/1671288
- https://web.archive.org/web/20131005224909/http://www.davekoelle.com/alphanum.html
- http://snipplr.com/view/36012/javascript-natural-sort/
- http://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
Graças à boa resposta do Shog9, que me colocou na direção "certa", acredito
Resposta (em 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 para um número resulta no 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 de comparação de classificação.
if (x == y) {
return 0;
}
return x > y ? 1 : -1;
Você deve usar > ou < e == aqui.Então 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;
});
Eu estava preocupado com isso há muito tempo, então finalmente pesquisei isso e lhe dei uma razão prolixa de por que as coisas são como são.
De especificação:
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. O operador triplo igual aplicado a strings retorna verdadeiro se os argumentos forem exatamente iguais (mesmo comprimento e mesmos caracteres nas posições correspondentes).
Então ===
funcionará nos casos em que estamos tentando comparar strings que podem ter chegado de fontes diferentes, mas que sabemos que eventualmente terão os mesmos valores - um cenário bastante comum para strings embutidas em nosso código.Por exemplo, se tivermos uma variável chamada connection_state
, e desejamos saber qual dos seguintes estados ['connecting', 'connected', 'disconnecting', 'disconnected']
está agora, podemos usar diretamente o ===
.
Mas há mais.Logo acima de 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.
Hum.E agora?Strings obtidas externamente podem, e provavelmente serão, ser unicodey estranhas, e nosso gentil ===
não lhes fará justiça.Entra localeCompare
para o resgate:
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.
dr;
Para comparar strings em javascript, use localeCompare
;se você sabe que as strings não possuem componentes não-ASCII porque são, por exemplo, constantes internas do programa, então ===
também funciona.
Função de seta ternária aninhada
(a,b) => (a < b ? -1 : a > b ? 1 : 0)
Na sua operação na sua pergunta inicial, você está realizando a seguinte operação:
item1.attr - item2.attr
Então, supondo que sejam números (ou seja,item1.attr = "1", item2.attr = "2") Você ainda pode usar o operador "===" (ou outros avaliadores estritos), desde que garanta o tipo.O seguinte deve funcionar:
return parseInt(item1.attr) - parseInt(item2.attr);
Se forem alfanuméricos, use localCompare().
list.sort(function(item1, item2){
return +(item1.attr > item2.attr) || +(item1.attr === item2.attr) - 1;
})
Como funcionam as 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
<!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>
var str = ['v','a','da','c','k','l']
var b = str.join('').split('').sort().reverse().join('')
console.log(b)