Быстрая сортировка в 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 entry was posted in AJAX, JavaScript, работа. Bookmark the permalink.

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

  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. Pingback: CakePHP. Как создать модуль без привязки к таблице в базе данных

  7. Snowcore says:

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

  8. kostik says:

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

  9. kostik says:

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

  10. kostik says:

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

  11. kostik says:

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

  12. kostik says:

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

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