400x Сортировка ускорения путем переключения a.localeCompare(b) на (a <b? -1: (a> b? 1: 0))

Переключение функции сортировки javascript из

myArray.sort(function (a, b) {
  return a.name.localeCompare(b.name);
});

к

myArray.sort(function (a, b) {
  return (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0));
});

Мне удалось сократить время сортировки массива элементов размером ~ 1700 в Chrome с 1993 по миллисекунды до 5 миллисекунд. Почти 400-кратное ускорение. К сожалению, это происходит за счет правильной сортировки неанглийских строк.

Очевидно, я не могу заблокировать свой пользовательский интерфейс в течение 2 секунд, когда я попытаюсь выполнить сортировку. Есть ли что-нибудь, что я могу сделать, чтобы избежать ужасно медленного localeCompare, но все же поддерживать поддержку локализованных строк?

Ответ 1

Значительное улучшение производительности можно получить, предварительно объявив объект-сборщик и используя его метод сравнения. EG:

const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' });
arrayOfObjects.sort((a, b) => {
  return collator.compare(a.name, b.name);
});

Вот эталонный скрипт, сравнивающий 3 метода:

const arr = [];
for (let i = 0; i < 2000; i++) {
  arr.push('test-${Math.random()}');
}

const arr1 = arr.slice();
const arr2 = arr.slice();
const arr3 = arr.slice();

console.time('#1 - localeCompare');
arr1.sort((a, b) => a.localeCompare(
  b,
  undefined, {
    numeric: true,
    sensitivity: 'base'
  }
));
console.timeEnd('#1 - localeCompare');

console.time('#2 - collator');
const collator = new Intl.Collator('en', {
  numeric: true,
  sensitivity: 'base'
});
arr2.sort((a, b) => collator.compare(a, b));
console.timeEnd('#2 - collator');

console.time('#3 - non-locale');
arr3.sort((a, b) => (a < b ? -1 : (a > b ? 1 : 0)));
console.timeEnd('#3 - non-locale');

Ответ 2

Эффективный подход, который я нашел при работе с/в основном/латинскими символами, заключается в использовании оператора всякий раз, когда обе строки соответствуют определенному регулярному выражению. EG: /^[\w-.\s,]*$/

Это намного быстрее, если обе строки соответствуют выражению, и в худшем случае это выглядит немного медленнее, чем слепой вызов localeCompare.

Пример здесь: http://jsperf.com/operator-vs-localecompage/11

Обновление: кажется, что Intl.Collator в настоящее время является лучшим вариантом для повышения производительности из всех возможных: https://jsperf.com/operator-vs-localecompage/22

Ответ 3

Трудно узнать самый быстрый вид, не видя данные, которые вы сортируете. Но у jsperf есть много хороших тестов, показывающих различия в производительности между типами сортировки: http://jsperf.com/javascript-sort/45 http://jsperf.com/sort-algorithms/31

Однако ни одна из них не учитывает локализованные строки, и я бы предположил, что нет простого способа сортировать локализованные строки, и localeCompare, вероятно, является лучшим решением для этого.

В обзоре mozilla говорится: "При сравнении большого количества строк, например при сортировке больших массивов, лучше создать объект Intl.Collator и использовать функцию, предоставляемую свойством сравнения". https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare

Но, перейдя к ссылке Intl.Collator, она показывает, что это не поддержка firefox/safari https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator

вы можете попробовать использовать некоторые параметры на localCompare, чтобы ускорить работу. Но я только что сделал быстрый тест, изменивший уровень чувствительности, и похоже, что он не улучшит производительность:

list.sort(function(a, b) {
  return a.localeCompare(b, {sensitivity:'base'});
});

http://jsperf.com/sort-locale-strings

Ответ 4

Попробуйте отсортировать его в 2 этапа:

  • С оператором: как вы сказали, он будет в 400 раз быстрее
  • Затем с localCompare(): теперь это меньше сравнений, потому что массив в основном отсортирован.

Примечание. Я думаю, что localCompare() будет вызван в основном по крайней мере с одной строкой, которая не является английской. Поэтому количество вызовов localCompare() с 2 английскими строками должно быть значительно уменьшено.

Вот код:

myArray.sort(function(a, b) {
  return (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0));
});

myArray.sort(function(a, b) {
  return a.name.localeCompare(b.name);
});

Это решение имеет то преимущество, что оно является коротким и простым в использовании. Это будет эффективно, если массив содержит в основном английские строки. Чем больше не английских строк, тем менее полезным будет первый сорт. Но, как легко добавить в свои скрипты, также легко увидеть, стоит ли этот подход.

Теперь, если бы я был вами, я также использовал бы Intl.Collator, поскольку он называется намного быстрее, чем localCompare(), когда у вас есть много сравнений.

Ответ 5

Я не знаю, что вы все еще ищете решение этой проблемы

// Defaulted to ascending
// 1 asc | -1 desc
var direction = 1; 
myArray.sort(function (a, b) {
  return a.name.localeCompare(b.name) === 1 ? direction : -1 * direction;
});

я добавил проверку === 1 на ваш код, и это улучшило perf 400x, что означает, что оба имеют сопоставимые первичные номера.

Числа Perf с localeCompare arr size: 3200 Среднее время прошло 10 повторений: 60 мс

Числа Perf s > подход. Время avg заняло 55 мс