Страницы

Поиск по вопросам

понедельник, 8 октября 2018 г.

Сортировка массивов русских символов и строк с участием буквы Ё

Простой пример кода, попробуем отсортировать массив русских символов:
var a = new char[] { 'д', 'е', 'ё', 'ж' }; var b = a.OrderBy(x => x).ToList(); Console.WriteLine(string.Concat(b));
Выдаст этот простой код неожиданное на первый взгляд дежё, но тут c# как раз верен стандартам, потому что код буквы ё больше, чем коды всех остальных букв русского алфавита.
Попробуем отсортировать массив строк, одна из которых содержит букву ё
var a = new string[] { "жар", "дом", "ели", "ёлка", }; var b = a.OrderBy(x => x).ToList(); Console.WriteLine(string.Join(" ", b));
Получаем ожидаемое дом ели ёлка жар. Вроде бы строки сортируются ожидаемым образом.
Попробуем ели заменить на ель. Получим прекрасное дом ёлка ель жар. Очевидно, при сортировке строк е и ё считаются одним символом, во втором случае ёлка становится перед ель потому что к идёт раньше ь
Моё наивное понимание предполагаемого алгоритма сортировки массива строк подсказывает, что он должен использовать тот же алгоритм сравнения кодов символов, что и сортировка массива символов. Этого, очевидно, не происходит. Ожидаемой модификацией алгоритма был бы учёт того, что ё в русском алфавите находится всё-таки не там, где она находится в unicode. А на самом деле имеем реализацию, где е и ё - это один символ.
Меня интересует несколько вопросов. Где конкретно определён алгоритм сортировки строк? Мои путешествия по ReferenceSource увели меня куда-то в цппшные недра GitHub'а CLR, не уверен, что двигался верно. Почему было принято решение принимать е и ё за один символ, а не осуществлять честную сортировку? Это чьё-то волевое решение или это всё-таки определено в какой-то из спецификаций?
Понимаю, что не все вопросы подразумевают наличие чёткого ответа у обычных участников сообщества, но ссылаюсь сюда
А может быть, что я вообще всё интерпретировал неверно, поправьте в таком случае.
Спасибо.


Ответ

Здесь причиной вашего удивления является не странность алгоритмов BCL, а имплементация стандарта Unicode.
Документация стандарта Unicode Unicode® Technical Standard #10 / Unicode Collation Algorithm гласит (перевод мой):
1.1 Многоуровневое сравнение Сортировка, требуемая человеческими языками, сложна. Чтобы правильно её имплементировать, используется многоуровневый алгоритм сравнения. При сравнении двух слов, самым важным являются базовые буквы, например, отличие между И и Е. Акценты обычно игнорируются, если базовые буквы не совпадают. Различие в регистре (прописные/строчные) также обычно игнорируются, если базовые буквы или их акценты различны. Что делать с пунктуацией, зависит от обстоятельств. В некоторых ситуациях, точка или запятая считается как бы отдельной базовой буквой. В других ситуациях пунктуация игнорируется, если и так есть отличия в базовых буквах, акцентах или регистре. В случае равенства иногда проводится финальное сравнение: если других различий нет, сравниваются (нормализованные) code point'ы.
Таким образом, сравнение слов проводится следующим образом:
Отбрасываются различия регистра и акценты. Если сравнение установило, какое из слов больше, конец алгоритма. Возвращаются назад акценты, проводится повторное сравнение. Если сравнение установило, какое из слов больше, конец алгоритма. Возвращается назад регистр, проводится повторное сравнение. Если сравнение установило, какое из слов больше, конец алгоритма.
и. т. д.
Для русского языка по «принципу кроссворда» Ё считается акцентированным вариантом Е, а Й — акцентированным вариантом И
Такой выбор стандарт для русского языка в Unicode, надеюсь, был согласован с лингвистами.
Как это поменять в .NET — как заставить считать Ё отдельной буквой, расположенной между Е и Ж, я сходу не скажу. (Но смотрите соседний ответ.)

Кстати, имплементация того или иного стандарта Unicode — фича не языка, а системы. BCL просит систему сравнить строки, чтобы не дублировать имплементацию Unicode. Это значит, что одна и та же программа, будучи проинсталлированной на Windows 7 и Windows 10, может вести себя по-разному по отношению к сортировке.

Уточнение
Для русской локали Й считается отдельной от И буквой, в то время как для английской Й считается акцентированным вариантом И. Буква Ё и там, и там считается акцентированным вариантом Е. Пример:
var strings = new[] { "Иа", "Йокогама", "Италия", "Ель", "Ёлочка" }; var en = CultureInfo.GetCultureInfo("en-US"); var ru = CultureInfo.GetCultureInfo("ru-RU"); var orderEn = strings.OrderBy(s => s, StringComparer.Create(en, false)); Console.WriteLine("en-US: " + string.Join(" ", orderEn)); var orderRu = strings.OrderBy(s => s, StringComparer.Create(ru, false)); Console.WriteLine("ru-RU: " + string.Join(" ", orderRu));
выдаёт такой результат:
en-US: Ёлочка Ель Иа Йокогама Италия ru-RU: Ёлочка Ель Иа Италия Йокогама

Мораль этой истории: при сортировке строк всегда указывайте локаль!

Комментариев нет:

Отправить комментарий