ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Category:

JavaScript: интернационализация, сортировка слов с Е и Ё

Прочел подраздел 14.6 «Intl: интернационализация в JavaScript» четырнадцатого раздела «Разное» первой части «Язык программирования JavaScript» учебника по JavaScript.

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

Под термином «интернационализация» в языках программирования, в том числе и в JavaScript, подразумевается настройка представления дат, временных отрезков, чисел и валют для разных стран, а также порядок сравнения букв в алфавитах разных стран, сортировки слов в разных языках общения. В языке программирования JavaScript для этих целей существует специальный объект Intl. Этот объект содержит функции-конструкторы Intl.Collator (для зависимого от языка общения сравнения букв и строк, сортировки), Intl.DateTimeFormat (для зависимого от языка общения представления даты и времени), Intl.NumberFormat (для зависимого от языка общения представления чисел и валют).

В этом посте из всего перечисленного выше богатства возможностей я немного затрону только особенности работы объектов, полученных с помощью функции-конструктора Intl.Collator, а, конкретнее, некоторые особенности сортировки строк на русском языке.

В подразделе 5.3 «Строки» учебника сказано, что внутренний формат (кодировка) для строк в языке программирования JavaScript — всегда UTF-16. Это один из способов кодирования (форм представления) символов Юникода. Лично я, когда мне нужно посмотреть таблицу символов Юникода онлайн, пользуюсь вот этим сайтом (очень удобный, я о нем уже неоднократно писал ранее):
https://unicode-table.com/ru/

Быстрый переход к той части таблицы, где находится русский алфавит:
https://unicode-table.com/ru/#cyrillic
https://unicode-table.com/ru/blocks/cyrillic/

У Юникода в части русского алфавита (одного из алфавитов, созданных на основе кириллицы) есть общеизвестные особенности и проблемы.

В принципе, в таблице Юникода кириллические буквы расположены в том порядке, в каком они расположены в русском алфавите. Это удобно для программирования сортировки слов на русском языке, потому что, как известно, при сортировке слова сравниваются побуквенно, а при сравнении букв сравниваются коды букв (числа) из таблицы Юникода. Но есть, как уже говорилось выше, особенности и проблемы (исключения).

Главная особенность, которую нужно знать: прописные (большие) буквы сгруппированы отдельно, а строчные (маленькие) буквы — тоже отдельно. При этом группа прописных букв идет в таблице Юникода раньше (их коды меньше), чем группа строчных букв. Эти группы примыкают друг к другу.

То есть буквы расположены не вот так (как обычно в русском алфавите):
АаБбВбГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя

А вот так:
АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя

В результате получается, что при сравнении букв оказывается верным неравенство "а" > "Я", что противоречит алфавитному порядку, по которому должно быть верным противоположное неравенство "а" < "Я", ведь и прописная буква «Я», и строчная буква «я» в русском алфавите находятся в самом конце, а потому должны иметь самые большие коды и быть больше любых других букв русского алфавита.

Я пометил красным буквы «Ё» и «ё» в ряду букв выше неспроста. В Юникоде они вынесены из вышеуказанного порядка букв. Русские буквы в таблице Юникода, на самом деле, расположены так:
Ё...АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя...ё
Многоточием я обозначил тот факт, что между буквой «Ё» и началом группы прописных русских букв расположено 14 других символов. А между концом группы строчных русских букв и буквой «ё» расположен один другой символ.

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

* * *

Итак, посмотрим, как происходит сортировка русских слов по умолчанию, то есть при этом учитывается только вышеизложенное расположение русских букв в таблице Юникода:
let arr = ["абажур", "Яма", "яма", "Абажур"];

arr.sort();   // сортировка по умолчанию

alert( arr ); // Абажур,Яма,абажур,яма
Тут у всех слов первые буквы разные, а потому сортировка идет именно по кодам этих первых букв, в соответствии со следующим неравенством: "А" < "Я" < "а" < "я". Если бы первые буквы у сравниваемых слов были бы одинаковыми, сравнение перешло бы ко второй букве и так далее.

Теперь добавим в тестовый массив русских слов слова, начинающиеся на буквы «е», «ё», «Е» и «Ё»:
let arr = ["абажур", "Яма", "яма", "Абажур", "еда", "ёж", "Езда", "Ёлка"];

arr.sort();   // сортировка по умолчанию

alert( arr ); // Ёлка,Абажур,Езда,Яма,абажур,еда,яма,ёж
Всё отсортировалось в соответствии с расположением первых букв этих слов в таблице Юникода. С одной стороны, всё логично, а с другой стороны — это не то, что нам обычно требуется, потому что слова отсортированы не в соответствии с порядком букв в русском алфавите.

* * *

Попробуем воспользоваться для сортировки русских слов объектом, полученным от функции-конструктора Intl.Collator:
let arr = ["абажур", "Яма", "яма", "Абажур", "еда", "ёж", "Езда", "Ёлка"];

let collator = new Intl.Collator("ru");
arr.sort( collator.compare );

alert( arr ); // абажур,Абажур,еда,ёж,Езда,Ёлка,яма,Яма
Так гораздо лучше.

На первый взгляд, всё понятно и работает, как надо. Однако, если копнуть поглубже, можно отыскать кучу разных тонкостей, некоторые из которых язык JavaScript позволяет настроить, а некоторые — не позволяет.

Например, для пары слов абажур,Абажур или для пары слов яма,Яма сортировку с помощью функции (метода) collator.compare можно настроить так, что в каждой паре слово, начинающееся с прописной буквы, встанет первым:
let arr = ["абажур", "Яма", "яма", "Абажур", "еда", "ёж", "Езда", "Ёлка"];

let collator = new Intl.Collator("ru", { caseFirst: "upper" });
arr.sort( collator.compare );

alert( arr ); // Абажур,абажур,еда,ёж,Езда,Ёлка,Яма,яма

Значит ли такая настройка, что прописные буквы буквы стали «весить» при сортировке больше строчных? Нет, всё сложнее. Если бы прописные буквы стали «весить» больше строчных, то четверка слов еда,ёж,Езда,Ёлка должна была бы после сортировки выглядеть как Езда,Ёлка,еда,ёж, а этого не произошло. Я не смог настроить функцию-конструктор Intl.Collator так, чтобы после сортировки получить Езда,Ёлка,еда,ёж в вышеприведенном коде.

Как тогда в данном случае работает сортировка русских слов?

Я представляю себе это как процесс из двух шагов:

1) На первом этапе регистр буквы не имеет значения, то есть, к примеру, буквы «А» и «а» считаются одной и той же буквой. Единственное уточнение: четыре буквы «Ё», «ё, «Е» и «е» на этом этапе тоже считаются одной и той же буквой.

(Правила русской орфографии и пунктуации действуют с 1956 года. Но в 2006 году их довольно сильно подредактировали. В статье википедии про букву «Ё» можно найти выдержки о правилах применения этой буквы из обеих этих редакций правил. В редакции правил от 2006 года сказано, что «В словарях слова с буквой ё размещаются в общем алфавите с буквой е, напр.: еле, елейный, ёлка, еловый, елозить, ёлочка, ёлочный, ель; веселеть, веселить(ся), весёлость, весёлый, веселье.». Отсюда и сделан вывод, что буквы «Ё» и «Е» при сортировке следует считать одной и той же буквой. Как я понимаю, это реализовано и в поведении объекта, получаемого от функции-конструктора Intl.Collator.)

2) Если на первом этапе сортировки некоторые слова определены как равные (содержат одинаковые буквы, имеют одинаковую длину), то анализируются дополнительные признаки (если они указаны программистом), в том числе регистр букв (caseFirst: "upper" из примера выше). В случае одинаковых слов, но в которых на одной и той же позиции есть буквы «е» и «ё», первым должно быть слово с буквой «е» (например, еж,ёж или Еж,Ёж).

Таким образом, на первом этапе в четверке слов еда,ёж,Езда,Ёлка сначала сравниваются первые буквы слов. Эти буквы, на основании вышеизложенного, считаются одной и той же буквой. Поэтому переходим к сравнению вторых букв указанных слов. Здесь оказывается верным неравенство "д" < "ж" < "з" < "л", поэтому слова оставляются в указанном порядке, сортировки не требуется, они уже и так отсортированы. Второй этап процесса сортировки не происходит, потому что среди этих четырех слов нет одинаковых (часть из них различается по длине, а совпадающие по длине имеют в своем составе разные буквы).

* * *

В принципе, никто не мешает вместо collator.compare написать свою функцию, реализовать там любое желаемое поведение и передать ее функции arr.sort в качестве параметра. Но, как отметили в комментариях к обсуждаемому подразделу учебника, такое решение, скорее всего, будет работать медленнее встроенного. Впрочем, наверное, это важно лишь для достаточно больших проектов.
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments