Як сортувати рядки в JavaScript


344

У мене є список об'єктів, які я хочу сортувати на основі поля attrрядка типу. Я спробував використовувати-

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

але виявлено, що -не працює з рядками в JavaScript. Як можна сортувати список об'єктів на основі атрибута з рядком типу?


1
дивіться JavaScript case insensitive string comparisonна stackoverflow.com/questions/2140627/…
Adrien Be

Для швидкого "інтернаціоналізованого" рішення (я частково думаю, оскільки це може не охоплювати всі акценти у світі), ви можете просто проігнорувати акценти, тобто видалити їх. Тоді виконайте лише порівняння рядків, дивіться Javascript : remove accents/diacritics in stringsна stackoverflow.com/questions/990904/…
Adrien Be

2
Смішний досить Jeff Atwood сам написав в блозі про це загальному випуску в 2007 році, см blog.codinghorror.com/sorting-for-humans-natural-sort-order
Adrien Be

Відповіді:


621

Використовуйте String.prototype.localeCompareна прикладі:

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

Ми змушуємо a.attr бути рядком, щоб уникнути винятків. localeCompareпідтримується з Internet Explorer 6 та Firefox 1. Можливо, ви також побачите такий код, який не відповідає локалі:

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

81
Перш ніж хто робить ту ж помилку поспішної , як я зробив, це локальний адресу Порівняє, що не localCompare.
ento

12
Першим рішенням вважатиметься, що "A" має бути після "z", але перед "Z", оскільки він проводить порівняння за значенням ASCII символу. localeCompare()не стикається з цією проблемою, але не розуміє числових значень, тому ви отримаєте ["1", "10", "2"], як при сортуванні порівнянь на більшості мов. якщо ви хочете сортування для призначеного для користувача інтерфейсу передньої частини, заглянути в alphanum / алгоритм природною сортування stackoverflow.com/questions/4340227 / ... або stackoverflow.com/questions/4321829 / ...
Dead.Rabit

2
Зауважте, що localeCompare()підтримується лише в сучасних браузерах: IE11 + під час написання, див. Developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Adrien Be

3
Ні, я маю в виду перший рядок таблиці, @Adrien - IE підтримує localeCompare()повернення багато версій, але не підтримує не вказавши локаль до версії 11. Зверніть увагу також на питання , які пов'язані з Dead.Rabit.
Shog9

3
@ Shog9 мені погано, здається, він підтримується з IE6! дивіться (прокручування вниз / пошук у localeCompare () метод на msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx . Однак слід зазначити одне, що в старих реалізаціях, де ми не використовуємо аргументи локалів та параметрів (той, який застосовувався до IE11) , використовуваний порядок локалізації та сортування повністю залежить від реалізації , інакше кажучи: Firefox, Safari, Chrome & IE НЕ сортувати рядки в одному порядку. дивіться code.google.com/p/v8/isissue/detail?id=459
Adrien Be

166

Оновлена ​​відповідь (жовтень 2014 р.)

Мене дуже дратував цей порядок природного сортування, тому мені знадобилося досить багато часу, щоб дослідити цю проблему. Я сподіваюся, що це допомагає.

Довга коротка історія

localeCompare()підтримка персонажів є поганою, просто використовуйте його. Як зазначав Shog9, відповідь на ваше запитання:

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

Помилки знайдені у всіх спеціальних реалізаціях JavaScript "природний порядок сортування рядків"

Існує досить багато спеціалізованих реалізацій, які намагаються зробити порівняння рядків точніше називати "природним порядком сортування рядків"

"Граючи" з цими реалізаціями, я завжди помічав якийсь дивний вибір "природного порядку сортування", а точніше помилки (або упущення в кращих випадках).

Зазвичай спеціальні символи (пробіл, тире, амперсанд, дужки тощо) не обробляються правильно.

Потім ви знайдете їх змішаними в різних місцях, як правило, це:

  • деякі будуть між великим регістром "Z" і малі "a"
  • деякі знаходяться між величиною "9" та великою літерою "A"
  • деякі будуть після малого "z"

Коли можна було б очікувати, що спеціальні символи будуть "згруповані" разом в одному місці, за винятком простору спеціального символу (який завжди був би першим символом). Тобто, або всі перед цифрами, або всі між цифрами та літерами (малі та великі регістри "разом" одна за одною), або всі після літер.

Мій висновок полягає в тому, що всі вони не забезпечують послідовного порядку, коли я починаю додавати ледь незвичні символи (наприклад, символи з діакритикою чи графіками, такими як тире, знак оклику тощо).

Дослідження спеціальних реалізацій:

Реалізація "рідного порядку сортування" у веб-переглядачах через localeCompare()

localeCompare()найстаріша реалізація (без аргументів локалів та параметрів) підтримується IE6 +, див. http://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (прокручуй униз до localeCompare ( ) метод). Вбудований localeCompare()метод робить набагато кращу роботу при сортуванні навіть міжнародних та спеціальних символів. Єдина проблема використання localeCompare()методу полягає в тому, що "використовуваний порядок локалізації та сортування повністю залежить від реалізації". Іншими словами, при використанні localeCompare, наприклад stringOne.localeCompare (stringTwo): Firefox, Safari, Chrome і IE мають інший порядок сортування для Strings.

Дослідження власних реалізацій браузера:

Складність "строкового природного порядку сортування"

Реалізація твердого алгоритму (що означає: послідовне, але також охоплює широкий діапазон символів) - дуже складне завдання. UTF8 містить понад 2000 символів і охоплює понад 120 скриптів (мов) . Нарешті, є певна специфікація для цих завдань, вона називається "Алгоритм зібрання Unicode", який можна знайти за адресою http://www.unicode.org/reports/tr10/ . Ви можете дізнатися більше про це в цьому питанні, яке я опублікував /software/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order

Остаточний висновок

Отож, враховуючи поточний рівень підтримки, що надається спеціальними реалізаціями JavaScript, на які я натрапив, ми, мабуть, ніколи не побачимо нічого, що наблизиться до підтримки всіх цих символів та сценаріїв (мов). Тому я б скоріше скористався нативним методом localeCompare () браузера. Так, у нього є і недолік невідповідності роботи у веб-переглядачах, але базове тестування показує, що воно охоплює набагато ширший діапазон символів, що дозволяє чіткі та значущі замовлення на сортування.

Отже, як зазначав Shog9, відповідь на ваше запитання:

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

Подальше читання:

Завдяки приємній відповіді Shog9, яка переконала мене в правильному напрямку


38

Відповідь (в сучасному ECMAScript)

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

Або

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

Опис

Переміщення булевого значення в число дає наступне:

  • true -> 1
  • false -> 0

Розглянемо три можливі схеми:

  • x більший ніж y: (x > y) - (y < x)-> 1 - 0->1
  • x дорівнює y: (x > y) - (y < x)-> 0 - 0->0
  • x менше, ніж y: (x > y) - (y < x)-> 0 - 1->-1

(Альтернатива)

  • x більший ніж y: +(x > y) || -(x < y)-> 1 || 0->1
  • x дорівнює y: +(x > y) || -(x < y)-> 0 || 0->0
  • x менше, ніж y: +(x > y) || -(x < y)-> 0 || -1->-1

Тож ці логіки еквівалентні типовим функціям порівняння сортування.

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

1
Коли я коментував попередню відповідь, що використовував цей трюк, відповіді, що стосуються лише коду, можна зробити більш корисними, пояснивши, як вони працюють.
Дан Даскалеску

Доданий опис
mpyw

Чи можете ви прокоментувати, що це краще чи гірше ніж localeCompare?
Ран Лоттем

3
@RanLottem localeCompareі стандартне порівняння дають різні результати. Чого ви очікуєте? ["A", "b", "C", "d"].sort((a, b) => a.localeCompare(b))сортує в алфавітному порядку, нечутливому до регістру, а ["A", "b", "C", "d"].sort((a, b) => (a > b) - (a < b))в порядку кодової точки
mpyw

Я бачу, це, здається, є головним моментом. Будь-яке уявлення про відмінності у виконанні?
Ран Лоттем

13

Ви повинні використовувати> або <і == тут. Тож рішення було б:

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
З іншого боку, це не обробляє порівняння рядків і чисел. Наприклад: 'Z' <9 (помилково), 'Z'> 9 (теж помилково ??), 'Z' == 9 (також помилково !!). Дурний NaN у JavaScript ...
Катол


7

оскільки рядки можна порівнювати безпосередньо в JavaScript, це зробить роботу

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

віднімання у функції сортування використовується лише тоді, коли потрібне не алфавітне (числове) сортування, і, звичайно, воно не працює з рядками


6

Я довго це турбував, тож я нарешті дослідив це і даю тобі цю довгомотку причину, чому все так, як вони є.

З специфікації :

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)

Тож зараз ми переходимо до 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.

Це воно. Оператор потрійних рівних, застосований до рядків, повертає true, якщо аргументи точно такі ж рядки (однакова довжина та однакові символи у відповідних позиціях).

Так ===буде працювати в тих випадках, коли ми намагаємося порівнювати рядки, які, можливо, надійшли з різних джерел, але які, як ми знаємо, з часом матимуть однакові значення - достатньо поширений сценарій для вбудованих рядків у нашому коді. Наприклад, якщо у нас є змінна назва connection_state, і ми хочемо знати, в якому з наступних станів ['connecting', 'connected', 'disconnecting', 'disconnected']знаходиться зараз, ми можемо безпосередньо використовувати ===.

Але є більше. Трохи вище 11.9.4, є коротка примітка:

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.

Хм. Що тепер? Зовнішні струни можуть, і, швидше за все, будуть дивними одноманітними, і наша ніжна ===не зробить їх справедливими. На localeCompareдопомогу приходить :

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. 

Ми можемо піти додому зараз.

tl; dr;

Для порівняння рядків у javascript використовуйте localeCompare; якщо ви знаєте, що в рядках немає компонентів, що не належать до ASCII, тому що вони є, наприклад, внутрішніми константами програми, тоді вони ===також працюють.


0

Під час свого початкового запитання ви виконуєте наступну операцію:

item1.attr - item2.attr

Отже, припускаючи, що це числа (тобто item1.attr = "1", item2.attr = "2") Ви все одно можете використовувати оператора "===" (або інших суворих оцінювачів) за умови введення типу. Слід працювати:

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

Якщо вони є alphaNumeric, тоді використовуйте localCompare ().


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

Як вони працюють зразки:

+('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
Відповіді, що стосуються лише коду, можна зробити кориснішими, пояснивши, як вони працюють.
Дан Даскалеску

-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
Додайте трохи інформації про те, як це вирішить питання до вашої відповіді. Відповіді, що стосуються лише коду, не вітаються. Дякую.
wayneOS

тут ви хочете замовити символи в рядку, що не є запитуваним. Ви можете домогтися цього сортування просто за допомогою "Array.sort", наприклад, 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)

Хоча цей код може вирішити питання, включаючи пояснення, як і чому це вирішує проблему, справді допоможе покращити якість вашої публікації та, ймовірно, призведе до збільшення кількості голосів. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз задає питання. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Дейв
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.