Быстрая сортировка в JavaScript

Не секрет, что спрос на приложения c приставкой “AJAX” постоянно растет. Волей-неволей приходится делать некоторые вещи неприспособленными для этого инструментами: характерным примером является Internet Explorer – программа, однозначно неприспособленная для выполнения кода, соответствующего стандарту и здравому смыслу. С одной из его проблем мне пришлось столкнуться: мне понадобилось отсортировать по заданному полю массив объектов. Выяснилось, что сортировка в IE работает в 100 раз медленнее, чем в FireFox. В результате было найдено решение, которое позволяет сортировать примерно в 6 раз быстрее, чем это делает Internet Explorer, ниже есть ссылка на библиотеку, которая реализует этот метод.

Проблема

Тривиальное

// Object а содержит 15000 элементов
a.sort(function(a, b){return b.item – a.item;})

заставило IE задуматься на 40(!) секунд (15000 взято в качестве теста, реальный массив, конечно, меньше). (FireFox выполнял тот же за 422ms, примерно в 100 раз быстрее!) Реализация сортировки на стороне сервера потребовала бы изменить много кода, так как поле и направление сортировки выбирались пользователем произвольно; это была проблема.

Решение

После недолгих поисков я обнаружил отличную статью Russel Lindsay: “Faster Javascript sorting”. Он использует очень простую вещь: переписать метод toString() для обращения к свойству объекта, превратить числа в строки ([1, 2, 10] в [’01’,’02’,’10’]) и использовать sort() без параметров для ускорения поиска. Вот результаты тестов:

Internet Explorer
41516ms – sort(function(a, b){return b.item - a.item;})
7031ms - sort() для массива, где числа конвертированы в строки
7047ms – sort() и reverse() для массива, где числа конвертированы в строки

FireFox
438ms – sort(function(a, b){return b.item - a.item;})
891ms - sort() для массива, где числа конвертированы в строки
954ms – sort() и reverse() для массива, где числа конвертированы в строки

Таким образом, фокус с дополнением чисел нолями до строк дает выигрыш примерно в 6 раз при сортировке в Internet Explorer, и проигрыш всего вдвое при сортировке в FF, что позволяет использовать код в обоих браузерах.

Russel Lindsay использует маску фиксированного размера, то есть массив ([1, 2, 10] превращается не в [01, 02, 10], а в [’00000000001’,’ 00000000002’,’ 00000000010’]), это ужасно по многим причинам, и память – не последняя из них. Так что я написал маленькую библиотеку на основе его кода, она расширяет Array() методами Array.sortIntAsc(property) и Array.sortIntDesc(property) для сортировки по числовому полю property, и методами Array.sortAsc(property) и Array.sortDesc(property) для сортировки по строковому полю property.

Пример

[js] var team = [
{name:'Bill', age:25},
{name:'John', age:21},
{name:'Ann', age:20}
];

alert([team[0].name,team[1].name,team[2].name]);
// Bill, John, Ann

team.sortIntAsc(‘age’);
alert([team[0].name,team[1].name,team[2].name]);
// Ann, John, Bill

team.sortAsc(‘name’);
alert([team[0].name,team[1].name,team[2].name]);
// Ann, Bill, John

[/js]

Мои 5 копеек

Этот метод, мне кажется, неплохое решение конкретной проблемы. Но это так же прекрасная иллюстрация: лучший способ сделать что-то с данными в Internet Explorer – это не делать совсем, то есть организовать модель так, что бы вся обработка данных (включая сортировку) осталась на стороне сервера. Иначе потом, когда IE будет отвратительно справляться с очевидными задачами, придется потратить немало сил на обход проблемы.

скачать библиотеку

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

13 Comments

  1. AKS says:

    Здравствуйте!
    Скачав и проанализировав вашу библиотечку, я, с вашего позволения, хотел бы немного полюбопытствовать вот о чем.
    Скажите, для чего нужно перед сортировкой массива переопределять метод toString прототипа Array?

  2. AKS says:

    “Чтобы не использовать sort(function())…” – да, это понятно, в этом вся суть. Я не совсем правильно выразился (не научился еще кратко излагать суть). А суть в следующем.
    Массив, требующий сортировки, состоит или из объектов (как в примере), или из массивов. Я предлагаю добавить возможность передавать в аргументе ссылку на класс (Object or Array), с которым придется иметь дело. Не придется обращаться к ненужным функциям.
    Вчера забыл отметить, что сама идея замечательная (поэтому и пишу, собственно говоря…).

  3. AKS says:

    Например, вызов в примере вот так:
    test (’sortIntDesc’, ‘item’, Object);
    И далее, по цепочке вызовов, аргумент передается и заменять нужно будет только toString именно его прототипа. Чтобы избежать ненужного, в данном случае, обращения в функциях к Array.prototype…

  4. AKS says:

    Функция test – с вашей тестовой странички. Я просто ничего не менял по-своему, проверял на вашем тесте.
    Ну а насчет “неудобно” пользоваться – я как раз к этому и надеялся свести разговор. Т.к. библиотеку, на самом деле, можно было бы превратить в один объект, который бы имел буквально пару-тройку методов, причем не “трогал” бы Array.prototype. Но это уже другая концепция, другой подход…

  5. kostik says:

    Наверное, можно и так, я не очень понимаю, как это будет и зачем, но с интересом посмотрю на реализацию.

  6. Snowcore says:

    Да, отличная идея от Russel Lindsay!
    Спсаибо за перевод.

  7. kostik says:

    Не то, чтобы это был перевод. Впрочем – о чем речь, пользуйтесь

  8. kostik says:

    Чтобы не использовать sort(function()) а просто sort() – так быстрее

  9. kostik says:

    А Вы, значит, шоу сейчас не смотрите 8-)

  10. kostik says:

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

  11. kostik says:

    Да, наверное, можно у так, но только зачем? В случае с библиотекой – просто подключили и ее, и обратились, когда нужно. В Вашем варианте – нужна внешняя функция, передача ей параметров и т.п. Причем выигрыша это не даст (или я его не вижу), а пользоваться будет неудобно.

    P.S. Я только не понял, при чем здесь функция test?

  12. kostik says:

    Thank you 8-)