Так как во время сортировки массива (а также обращения) яваскрипт не создает его копию, сортировать числа можно очень быстро. И это здорово — но скучно.
Интереснее становится, когда надо отсортировать большой массив объектов по нескольким вложенным правилам. Например, отсортировать кучу коктейлей по количеству в них водки, далее отсортировать по количеству ингредиентов, а затем еще и по алфавиту. Вопрос в том, как выразить такую логику.
Предположим, что нам нужно отсортировать массив объектов {x: число, y: число, z: число} сначала по x, потом по y потом по z. Есть как минимум три очевидных способа.
- Каскадный:
-
Так можно делать, когда интерпретатор поддерживает «stable sort» и сортировки не связаны (тут так и есть).array.sort(byZ).sort(byY).sort(byX)
- Одиночный:
-
UPD 2010-01-12: Конструкция хитрая, но работает. В яваскрипте (и еще, как минимум, в перле, руби, питоне и Си) операторarray.sort(function (a, b) { return byX(a, b) || byY(a, b) || byZ(a, b) })||не приводит аргументы к булеву типу, что позволяет клеить такие цепочки хоть до посинения.
- Раскрытый (он же «inlined»):
-
array.sort(function (a, b) { return a.x - b.x || a.y - b.y || a.z - b.z })
В хороших и зрелых браузерах они все работают быстро и сортируют одинаково правильно (ага!). Но в нехороших браузерах, что-то подтормаживает, а что-то даже и не сортирует.
Каскадный способ очень красив и даже не очень тупит, но не работает в Опере (включая зарелизенную десятку) и в Хроме (включая альфы четверки, они об этом знают). В третьем фаерфоксе, во всех ИЕ, а также в сафари три типа сортировки дают правильный порядок элементов. То есть придется париться с обертками.
Тесты показывают, что одиночный sort() почти в два раза быстрее каскадного. Раскрытый — еще быстрее раза в полтора-два-три (даже в хроме с его мегаумным В8). Забавно, что каскадный и одиночный способы сортировки дают одинаковые результаты в ИЕ 6 и 7 (в восьмом картина такая же как и в других браузерах).
var messed =
[
{x: 2, y: 0, z: 0},
{x: 2, y: 0, z: 1},
{x: 2, y: 0, z: 2},
{x: 2, y: 1, z: 0},
{x: 2, y: 1, z: 1},
{x: 2, y: 1, z: 2},
{x: 2, y: 2, z: 0},
{x: 2, y: 2, z: 1},
{x: 2, y: 2, z: 2},
{x: 0, y: 0, z: 0},
{x: 0, y: 0, z: 1},
{x: 0, y: 0, z: 2},
{x: 0, y: 1, z: 0},
{x: 0, y: 1, z: 1},
{x: 0, y: 1, z: 2},
{x: 0, y: 2, z: 0},
{x: 0, y: 2, z: 1},
{x: 0, y: 2, z: 2},
{x: 1, y: 0, z: 0},
{x: 1, y: 0, z: 1},
{x: 1, y: 0, z: 2},
{x: 1, y: 1, z: 0},
{x: 1, y: 1, z: 1},
{x: 1, y: 1, z: 2},
{x: 1, y: 2, z: 0},
{x: 1, y: 2, z: 1},
{x: 1, y: 2, z: 2}
]
var sorted =
[
{x: 0, y: 0, z: 0},
{x: 0, y: 0, z: 1},
{x: 0, y: 0, z: 2},
{x: 0, y: 1, z: 0},
{x: 0, y: 1, z: 1},
{x: 0, y: 1, z: 2},
{x: 0, y: 2, z: 0},
{x: 0, y: 2, z: 1},
{x: 0, y: 2, z: 2},
{x: 1, y: 0, z: 0},
{x: 1, y: 0, z: 1},
{x: 1, y: 0, z: 2},
{x: 1, y: 1, z: 0},
{x: 1, y: 1, z: 1},
{x: 1, y: 1, z: 2},
{x: 1, y: 2, z: 0},
{x: 1, y: 2, z: 1},
{x: 1, y: 2, z: 2},
{x: 2, y: 0, z: 0},
{x: 2, y: 0, z: 1},
{x: 2, y: 0, z: 2},
{x: 2, y: 1, z: 0},
{x: 2, y: 1, z: 1},
{x: 2, y: 1, z: 2},
{x: 2, y: 2, z: 0},
{x: 2, y: 2, z: 1},
{x: 2, y: 2, z: 2}
]
function byX (a, b) { return a.x - b.x }
function byY (a, b) { return a.y - b.y }
function byZ (a, b) { return a.z - b.z }
function single (a, b) { return byX(a, b) || byY(a, b) || byZ(a, b) }
function combined (a, b) { return a.x - b.x || a.y - b.y || a.z - b.z }
var m = messed.slice()
console.time('multiple')
for (var i = 0; i < 1000; i++)
{
m.sort(byZ),
m.sort(byY),
m.sort(byX)
}
console.timeEnd('multiple')
var s = messed.slice()
console.time('single')
for (var i = 0; i < 1000; i++)
{
s.sort(single)
}
console.timeEnd('single')
var c = messed.slice()
console.time('combined')
for (var i = 0; i < 1000; i++)
{
c.sort(combined)
}
console.timeEnd('combined')