Глаза страшат, а руки делают

ЧАСТЬ ВТОРАЯ


22. Сортировка массивов

  • метод sort();
  • создание функции сравнения;
  • сортировка многозначных чисел;
  • сортировка по алфавиту без учёта регистра;
  • сортировка по алфавиту с учётом буквы «Ё».

Метод sort()

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

Для сортировки значений массивов используется метод sort(). Сам по себе этот метод очень простой, но сортировка, которую он делает сам по себе, без аргументов, далеко не всегда может соответствовать нашим запросам.

Синтаксис

массив.sort([функцияСравнения])

Аргумент

функцияСравнения — функция, содержащая алоритм сравнения. Бывает нужна, когда алгоритм сравнения должен отличаться от данного по умолчанию. Эту функцию нужно писать самому.

Для начала рассмотрим метод в его простейшем виде — без дополнительной функции.

Примеры

1. Алфавитная сортировка

Этот метод изменяет исходный массив, поэтому для чистоты эксперимента снова воспользуемся клонированием-перегонкой.

var names = ["Петя", "Яша", "Миша", "Вася", "Аркаша", "Коля", "Боря"];
var clonNames = names.join("%&^").split("%&^");

Теперь к клону применим метод и выведем результат:

var sortNames = clonNames.sort();
document.write("Массив «как есть»: " + names + "<br>");
document.write("Отсортированный массив: " + sortNames);

Результат

2. Сортировка чисел

var numbers = [5, 8, 3, 7, 6, 2, 9, 1, 4];
var clonNumbers = numbers.join("%&^").split("%&&^");
var sortNumbers = clonNumbers.sort();
document.write("Массив «как есть»: " + numbers + "<br>");
document.write("Отсортированный массив: " + sortNumbers);

Результат


А теперь попробуем добавить к именам несколько слов с маленькой буквы, а к однозначным числам — несколько многозначных:

var words = ["юг", "север", "запад", "восток"];
var allWords = clonNames.concat(words).sort();
var numbers2 = [15, 38, 243, 87, 156, 420, 957, 16, 24];
var allNumbers = clonNumbers.concat(numbers2).sort();
document.write("Все слова: " + allWords + "<br>");
document.write("Все числа: " + allNumbers);

Результат


Видите? Слова с большой буквы обособились от слов с маленькой, а числа рассортировались не по значению, а как бы «по алфавиту».

Кто виноват?

Метод sort().

В качестве образца для сортировки он использует коды символов ASCII. В кодах маленькие буквы находятся после больших. Поэтому первичная сортировка получается по регистру символов, а вторичная — в строгом алфавитном порядке.

Что делать?

Писать функцию, варианты которой мы рассмотрим в следующих разделах.

Функции сравнения

Такая функция уже встроена в метод. Она сравнивает каждые два элемента в массиве и каждые два символа в элементе. Но, как я уже говорил, для образца она использует коды символов ASCII.

Вот схема кода, по которому происходит сравнение элементов для сортировки.

function compareElements (элемент1, элемент2) {
  if (условие, при котором элемент1 должен появиться перед элемент2)
    return -1;
  if (условие, при котором элемент1 должен появиться после элемент2)
    return 1;
  else
    return 0;  // Порядок элементов не меняется
}

Теперь наша задача — заполнить эту «коробку» таким кодом, который сортировал бы массив по нашим условиям. Дополнительная сложность в том, что эта функция может иметь только два аргумента: элемент1 и элемент2. Метод принимает в качестве аргумента только имя функции, без её аргументов:

массив.sort(compareElements)

Функция сравнения чисел по значениям

Это самый простой вариант функции.

Вот какие условия нам нужны для правильной сортировки чисел:

function compareNumbers (a, b) {
  if (a < b)
    return -1;
  if (a > b)
    return 1;
  else
    return 0;  // Порядок элементов не меняется
}

Собственно, это уже готовая функция, но можно сделать её ещё короче и проще.

Дело в том, что при возвращениях функции -1 и 1 принципиальное значение имеет не единица, а её знак (минус или плюс). Если она вместо этих единиц будет возвращать любое отрицательное или любое положительное число, результаты сравнения будут теми же. Поэтому достаточно написать:

function compareNumbers (a, b) {
  return a - b;
}

Смотрите: при этом условии если у нас a<b, то результат всегда будет меньше нуля, если a>b, — то больше, а при a==b выпадает нуль.

Проверим:

var allNumbers2 = clonNumbers.concat(numbers2).sort(compareNumbers);
document.write("Отсортированные числа: " + allNumbers2);

Результат

YES!

Сортировка текста без учёта регистра

Здесь есть проблема, связанная всё с той же слабой типизированностью языка. На какой «трубе» сидят наши a и b из функции? Тайна сия велика есть. И приходится применять старый добрый метод тык(). Причём обнаруживается, что в разных браузерах он иногда ведёт себя по-разному.

Допустим, что «a и b шушукались в строке». В этом случае можно попробовать для сравнения применить к ним метод toLowerCase().

Да, оказалось, что если представить a и b как строковые объекты, то функция их «переварит» и даже сосчитает!

function anyCase(a, b) {
if (a.toLowerCase() > b.toLowerCase())
  return 1;
if (a.toLowerCase() < b.toLowerCase())
  return -1;
else
  return 0;
}

Проверим функцию в работе:

var allWords2 = clonNames.concat(words).sort(anyCase);
document.write(allWords2);

Результат

Но в русском языке есть ещё одна проблема...

Проблема «Ё-моё»

Как известно, в байте 8 битов. А байты организуются парами: «старший» и «младший». Оссюда такая любовь к «дважды-восьмёркам», отсюда и «1024 метра в километре». А сколько букв в русском алфавите?

— Восемь байтов... то есть... ммм... по тридцать две на регистр, — ответил программист и посадил их в восьмибайтовую прокрустову клетку.

«Изгоем» оказалась буква Ё.

В результате в Unicode ряд А—Я занимает индексы 1040–1071, ряд а—я — индексы 1072–1103, большое Ё занимает позицию 1025, то есть раньше А, а маленькое ё — 1105, то есть вскоре после я.

Так они и будут сортироваться. Сначала Ё большое, потом алфавит верхнего регистра, потом — нижнего, а потом — ё маленькое. В нашей функции без учёта регистра все ё окажутся в конце.

Можно ли создать функцию для правильной сортировки буквы Ё?

Да. Хотя и не так просто.

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

Когда я зашёл в тупик в своих попытках, то обратился на форум «ТвойWeb». И буквально через несколько часов меня закидали разными функциями, такими же кривыми, как и моя, но с новыми остроумными и нестандартными идеями. В результате появилась эта функция, в которой основная идея принадлежит участнику форума под ником ETC. Всю «творческую лабораторию» можно посмотреть здесь.

function uniSort(a, b) {
var aCode = a.toLowerCase().replace('ё','е'+String.fromCharCode(1110));
var bCode = b.toLowerCase().replace('ё','е'+String.fromCharCode(1110));
if (aCode > bCode)
  return 1;
if (aCode < bCode)
  return -1;
else
  return 0;
}

Идея в том, что во время сортировки все ё заменяются на пару символов: е плюс некий символ, стоящий после кириллицы. Когда такая «сладкая парочка» встречается с одиноким е, то е уступает ей дорогу. Чтобы этот артефакт не проскочил в наш реальный массив, мы создаём для него особые переменные, а «на трубе остаются» наши старые знакомцы a и b.

Проверим (как на букву ё, так и на регистр):

var testForYo = ["тело", "ёлка", "ООН", "ёжик", "Ежов", "тёлка", "Лёвин", "Левин", "она", "ель"];
document.write(testForYo.sort(uniSort));

Результат


Итак, мы узнали:

как сортировать различные массивы, используя нестандартные функции сравнения.


К следующему уроку >>


 012028