Jak sortować ciągi w JavaScript


344

Mam listę obiektów, które chcę sortować na podstawie pola attrtypu string. Próbowałem użyć-

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

ale okazało się, że -nie działa z łańcuchami w JavaScript. Jak mogę posortować listę obiektów na podstawie atrybutu z ciągiem znaków?


1
patrz JavaScript case insensitive string comparisonna stackoverflow.com/questions/2140627/…
Adrien Be

W przypadku szybkiego rozwiązania „internacjonalizowanego” (chyba tylko częściowo, ponieważ może nie obejmować wszystkich akcentów na świecie), możesz po prostu zignorować akcenty, to znaczy je usunąć. Następnie wykonaj tylko porównanie ciągów, patrz Javascript : remove accents/diacritics in stringsna stackoverflow.com/questions/990904/...
Adrien Be

2
Zabawne, że Jeff Atwood sam napisał post na blogu o tym powszechnym problemie w 2007 roku, patrz blog.codinghorror.com/sorting-for-humans-natural-sort-order
Adrien Be

Odpowiedzi:


620

Użyj String.prototype.localeComparena przykład:

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

Zmuszamy a.attr jako ciąg znaków, aby uniknąć wyjątków. localeComparejest obsługiwany od wersji Internet Explorer 6 i Firefox 1. Możesz także zobaczyć następujący kod, który nie przestrzega ustawień regionalnych:

if (item1.attr < item2.attr)
  return -1;
if ( item1.attr > item2.attr)
  return 1;
return 0;

81
Zanim ktokolwiek popełni ten sam pospieszny błąd, co ja, to jest to lokalny e Porównaj, a nie localCompare.
ENTO

12
Pierwsze rozwiązanie uwzględni „A” po „Z”, ale przed „Z”, ponieważ dokonuje porównania wartości znaku ASCII. localeCompare()nie napotyka tego problemu, ale nie rozumie liczb, więc otrzymasz [„1”, „10”, „2”] jak przy sortowaniu porównań w większości języków. jeśli chcesz sortować dla interfejsu użytkownika, zajrzyj do algorytmu sortowania alphanum / natural stackoverflow.com/questions/4340227/... lub stackoverflow.com/questions/4321829/...
Dead.Rabit

2
Uwaga: localeCompare()jest obsługiwany tylko w nowoczesnych przeglądarkach: IE11 + w momencie pisania, patrz developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Adrien Be

3
Nie, mam na myśli pierwszą linię tabeli, @Adrien - IE obsługuje localeCompare()cofanie się o wiele wersji, ale nie obsługuje określania ustawień regionalnych aż do wersji 11. Zwróć też uwagę na pytania, z którymi łączył się Dead.Rabit.
Shog9,

3
@ Shog9 mój zły, wygląda na to, że jest obsługiwany od IE6! zobacz (metoda przewijania w dół / szukaj do localeCompare ()) na msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx . Należy jednak zauważyć, że w starych implementacjach, w których nie używamy argumentów ustawień narodowych i opcji (używanych przed IE11) , używane ustawienia regionalne i porządek sortowania są całkowicie zależne od implementacji , innymi słowy: Firefox, Safari, Chrome i IE robią NIE sortuj ciągów w tej samej kolejności. patrz code.google.com/p/v8/issues/detail?id=459
Adrien Be

166

Zaktualizowana odpowiedź (październik 2014 r.)

Byłem naprawdę zirytowany tą naturalną kolejnością sortowania ciągów, więc poświęciłem sporo czasu na zbadanie tego problemu. Mam nadzieję, że to pomoże.

Krótko mówiąc

localeCompare()obsługa postaci jest kiepska, po prostu jej używaj. Jak wskazano Shog9, odpowiedź na twoje pytanie brzmi:

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

Błędy znalezione we wszystkich niestandardowych implementacjach javascript „porządek sortowania ciągów znaków”

Istnieje całkiem sporo niestandardowych implementacji, które starają się porównywać ciągi bardziej precyzyjnie zwane „porządkiem sortowania ciągów naturalnych”

Kiedy „bawiłem się” tymi implementacjami, zawsze zauważyłem jakiś dziwny wybór „naturalnego porządku sortowania”, a raczej błędy (lub pominięcia w najlepszych przypadkach).

Zazwyczaj znaki specjalne (spacja, myślnik, znak ampersand, nawiasy itd.) Nie są przetwarzane poprawnie.

Zauważysz, że pojawiają się one pomieszane w różnych miejscach, zwykle mogą to być:

  • niektóre będą między wielkimi literami „Z” a małymi literami „a”
  • niektóre będą między „9” a dużymi literami „A”
  • niektóre będą po małych literach „z”

Gdyby można było oczekiwać, że wszystkie znaki specjalne zostaną „zgrupowane” razem w jednym miejscu, może z wyjątkiem spacji specjalnej postaci (która zawsze byłaby pierwszą postacią). To znaczy albo wszystkie przed cyframi, albo wszystkie między cyframi i literami (małe i wielkie litery są „razem” jedna po drugiej) lub wszystkie po literach.

Doszedłem do wniosku, że wszystkie nie zapewniają spójnego porządku, gdy zaczynam dodawać ledwo niezwykłe znaki (tj. Znaki z znakami diakrytycznymi lub znakami takimi jak myślnik, wykrzyknik i tak dalej).

Badanie niestandardowych implementacji:

Natywne implementacje przeglądarki „naturalny porządek sortowania ciągów” za pośrednictwem localeCompare()

localeCompare()najstarsza implementacja (bez argumentów ustawień narodowych i opcji) jest obsługiwana przez IE6 +, patrz http://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (przewiń w dół do localeCompare ( ) metoda). Wbudowana localeCompare()metoda znacznie lepiej radzi sobie z sortowaniem, nawet znaków międzynarodowych i specjalnych. Jedynym problemem związanym z tą localeCompare()metodą jest to, że „użyte ustawienia regionalne i porządek sortowania są całkowicie zależne od implementacji”. Innymi słowy, podczas korzystania z localeCompare, takiego jak stringOne.localeCompare (stringTwo): Firefox, Safari, Chrome i IE mają inną kolejność sortowania dla ciągów.

Badanie implementacji natywnych dla przeglądarki:

Trudność w „naturalnym porządku sortowania ciągów”

Wdrożenie solidnego algorytmu (co oznacza: spójny, ale także obejmujący szeroki zakres znaków) jest bardzo trudnym zadaniem. UTF8 zawiera ponad 2000 znaków i obejmuje ponad 120 skryptów (języków) . Wreszcie, istnieje pewna specyfikacja dla tych zadań, nazywa się to „Algorytm sortowania Unicode”, który można znaleźć na stronie http://www.unicode.org/reports/tr10/ . Więcej informacji na ten temat można znaleźć w tym pytaniu: /software/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order

Ostateczna konkluzja

Biorąc pod uwagę obecny poziom wsparcia zapewnianego przez niestandardowe implementacje javascript, z którymi się zetknąłem, prawdopodobnie nigdy nie zobaczymy, żeby cokolwiek zbliżyło się do obsługi wszystkich tych znaków i skryptów (języków). Dlatego wolałbym używać natywnej metody localeCompare () przeglądarki. Tak, ma tę wadę, że jest niespójna w różnych przeglądarkach, ale podstawowe testy pokazują, że obejmuje znacznie szerszy zakres znaków, umożliwiając solidne i znaczące porządki sortowania.

Jak wskazano Shog9, odpowiedź na twoje pytanie brzmi:

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

Dalsza lektura:

Dzięki ładnej odpowiedzi Shog9, która wydaje mi się, że „we właściwym kierunku”


38

Odpowiedź (w Modern ECMAScript)

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

Lub

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

Opis

Rzutowanie wartości logicznej na liczbę daje następujące wyniki:

  • true -> 1
  • false -> 0

Rozważ trzy możliwe wzorce:

  • x jest większy niż y: (x > y) - (y < x)-> 1 - 0->1
  • x jest równe y: (x > y) - (y < x)-> 0 - 0->0
  • x jest mniejsze niż y: (x > y) - (y < x)-> 0 - 1->-1

(Alternatywny)

  • x jest większy niż y: +(x > y) || -(x < y)-> 1 || 0->1
  • x jest równe y: +(x > y) || -(x < y)-> 0 || 0->0
  • x jest mniejsze niż y: +(x > y) || -(x < y)-> 0 || -1->-1

Te logiki są więc równoważne typowym funkcjom komparatora sortowania.

if (x == y) {
    return 0;
}
return x > y ? 1 : -1;

1
Jak skomentowałem wcześniejszą odpowiedź, w której wykorzystano tę sztuczkę, odpowiedzi zawierające tylko kod można uczynić bardziej użytecznymi, wyjaśniając, jak działają.
Dan Dascalescu

Dodano opis
mpyw

Czy możesz skomentować, czy jest to lepsze czy gorsze niż localeCompare?
Ran Lottem,

3
@RanLottem localeComparei porównanie standardowe dają różne wyniki. Czego oczekujesz? ["A", "b", "C", "d"].sort((a, b) => a.localeCompare(b))sortuje w kolejności alfabetycznej bez rozróżniania wielkości liter, a ["A", "b", "C", "d"].sort((a, b) => (a > b) - (a < b))robi to w porządku
współrzędnych kodowych

Widzę, że to wydaje się być głównym punktem spornym. Masz pomysł na różnice w wydajności?
Ran Lottem,

13

Powinieneś użyć> lub <i == tutaj. Tak więc rozwiązaniem byłoby:

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

1
Na marginesie, nie poradzi sobie z porównywaniem ciągów z liczbami. Na przykład: „Z” <9 (fałsz), „Z”> 9 (również fałsz?), „Z” == 9 (również fałsz !!). Silly NaN w JavaScript ...
Kato

7

Zagnieżdżona funkcja strzałki trójskładnikowej

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

7

ponieważ ciągi znaków można porównywać bezpośrednio w javascript, spełni to zadanie

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

odejmowanie w funkcji sortowania jest używane tylko wtedy, gdy pożądane jest sortowanie nie alfabetyczne (numeryczne) i oczywiście nie działa z łańcuchami


6

Martwiłem się tym przez długi czas, więc w końcu to zbadałem i podałem ci ten długi powód, dlaczego rzeczy są takie, jakie są.

Ze specyfikacji :

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)

Teraz przechodzimy do 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.

Otóż ​​to. Potrójny operator równości zastosowany do ciągów zwraca true, jeśli argumenty są dokładnie tymi samymi ciągami (ta sama długość i te same znaki w odpowiednich pozycjach).

Tak więc ===będzie działać w przypadkach, gdy próbujemy porównać ciągi, które mogły przybyć z różnych źródeł, ale które, jak wiemy, ostatecznie będą miały te same wartości - dość powszechny scenariusz dla wbudowanych ciągów w naszym kodzie. Na przykład, jeśli mamy zmienną o nazwie connection_state, i chcemy wiedzieć, który z poniższych stanów ['connecting', 'connected', 'disconnecting', 'disconnected']jest w tej chwili, możemy bezpośrednio użyć ===.

Ale jest coś więcej. Tuż powyżej wersji 11.9.4 jest krótka notatka:

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 Co teraz? Zewnętrznie uzyskane struny mogą i najprawdopodobniej będą dziwnym jednorożcem, a nasi delikatni ===nie oddadzą im sprawiedliwości. Przychodzi localeComparena ratunek:

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. 

Możemy już iść do domu.

tl; dr;

Aby porównać ciągi w javascript, użyj localeCompare; jeśli wiesz, że ciągi nie zawierają żadnych elementów spoza ASCII, ponieważ są one na przykład wewnętrznymi stałymi programu, to ===również działa.


0

W swojej operacji w początkowym pytaniu wykonujesz następującą operację:

item1.attr - item2.attr

Zakładając, że są to liczby (tj. Item1.attr = „1”, item2.attr = „2”) Nadal możesz używać operatora „===” (lub innych ścisłych ewaluatorów) pod warunkiem, że zapewnisz typ. Następujące powinny działać:

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

Jeśli są alphaNumeric, użyj localCompare ().


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

Jak działają próbki:

+('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

3
Odpowiedzi zawierające tylko kod można uczynić bardziej użytecznymi, wyjaśniając, w jaki sposób działają.
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>

1
Dodaj informacje o tym, jak to rozwiąże pytanie do twojej odpowiedzi. Odpowiedzi tylko z kodem nie są mile widziane. Dziękuję Ci.
wayneOS,

tutaj chcesz uporządkować znaki w ciągu, który nie jest pytany. Możesz to sortować, używając po prostu „Array.sort”, np. 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)

Chociaż ten kod może rozwiązać pytanie, w tym wyjaśnienie, w jaki sposób i dlaczego to rozwiązuje problem, naprawdę pomógłby poprawić jakość twojego postu i prawdopodobnie zwiększyłby liczbę głosów pozytywnych. Pamiętaj, że odpowiadasz na pytanie czytelników w przyszłości, a nie tylko osoby zadającej teraz pytanie. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia.
Dave
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.