Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save codedokode/ea09d0224a4df0c4442f to your computer and use it in GitHub Desktop.
Save codedokode/ea09d0224a4df0c4442f to your computer and use it in GitHub Desktop.
Особенности сравнения строк

Эта статья перенесена сюда: https://github.com/codedokode/pasta/blob/master/php/collation.md Ниже идет старая, неподдерживаемая версия статьи.


В программировании строки можно сравнивать. При этом обычно подразумевается алфавитное сравнение, то есть меньше то слово, которое идет раньше по алфавиту. Если начальные буквы совпадают, то меньше то слово, которое короче. Например, "аббат" < "аккорд", "кот" < "котёнок".

Если разобраться в теме сравнения строк на любых языках (а не только на русском), то все выглядит гораздо сложнее и появляется много особенностей, которые надо учитывать(по-английски эта тема называется «collation»). И проблема не в том, что там много букв из разных алфавитов, а в том что одни и те же буквы имеют разный порядок в разных языках. Например, буквы с точечками и черточками сравниваются по-разному: http://en.wikipedia.org/wiki/Alphabetical_order#Language-specific_conventions

В некоторых языках буквы вроде å идут после z, в некоторых между a и b. В некорых (английский) они имеют одинаковый вес с a. То есть сортировка зависит от языка.

В немецком, ß может быть равносильно ss, по крайней мере произносятся они одинаково.

В японском есть 2 алфавита (хирагана/катакана), обозначающих одни и те же слоги.

Лигатуры вроде Œ могут быть равносильны комбинации oe, а могут и не быть.

Также, одна буква (упомянутая выше å) может храниться в строке как один символ либо как комбинация 2 символов: кружочек + a (чтобы избежать проблем из-за этого, строки перед сравнением можно нормализовать, приведя к единому виду).

Как ты, надеюсь догадался к этому моменту, сравнение строк очень нетривиальная вещь. Потому для этого есть специальные библиотеки, самая известная из них это открытая ICU (мануал на англ. http://userguide.icu-project.org/ ). В PHP ты можешь воспользоваться ее возможностями с помощью расширения Intl (которое умеет не только сравнение строк, но и многое другое, например писать числа прописью): http://php.net/manual/ru/book.intl.php . По моему, в Windows версии это расширение идет в комплекте с php и надо только включить его в php.ini. В линуксе оно ставится отдельно.

В этом расширении есть класс Collator для сравнения строк по правилам указанного языка: http://php.net/manual/ru/class.collator.php

Он умеет сравнивать строки и сортировать массивы, в том числе применяя нормализацию, по правилам в выбранной локали. Вот пример кода, сравнивающего 2 строки с учетом языка. В нем мы создаем объект Collator, передавая ему локаль (краткий код языка и территории — в нашем случае это ru_RU (русский язык в России). Территорию надо указывать, так как например американский (en_US) и британский (en_GB) английский немного различаются. Посмотреть возможные коды локалей можно тут: http://demo.icu-project.org/icu-bin/locexp . Прочитать подробно на англ. тут: http://userguide.icu-project.org/locale . Корневая локаль под названием root позволяет сравнивать строки по обобщенным правилам).

Дополнительные опции (например, считать ли символы хираганы/катаканы разными или учитывается ли регистр букв) задаются через атрибуты методом setAttribute. Возможные атрибуты указаны тут в виде констант: http://php.net/manual/ru/class.collator.php#intl.collator-constants

Одна из опций — уровень сравнения. На первом (PRIMARY) уровне игнорируются различия между буквами с акцентами вроде å и a, а по умолчанию выставлен третий уровень, который их проверяет.

<?php

$collatorRu = new Collator('ru_RU');
$result = $collatorRu->compare("Ель", "Ёлка");
var_dump($result); // 1, Ель > Ёлка

$result2 = $collatorRu->compare("Е", "Ё");
var_dump($result2); // -1, Е < Ё

// Немецкий
$collatorDe = new Collator('de_DE');
// Игнорировать различия в написании букв и акценты
$collatorDe->setAttribute(Collator::STRENGTH, Collator::PRIMARY);
$result3 = $collatorDe->compare("ss", "ß");
var_dump($result3); // 0 - строки равны

Сам алгоритм сравнения подробно описан в UNICODE COLLATION ALGORITHM: http://www.unicode.org/reports/tr10/ (англ). Там в начале есть примеры всех этих сложностей со сравнением, интересно почитать.

Без него (расширения Intl), например в выражении if ($x > $y), php сравнивает строки побайтово, по кодам символов. То есть у кого код больше тот и ниже в списке. Вот получающийся порядок символов, например, для кириллицы: http://unicode-table.com/ru/#cyrillic

Видно что буква Ё там идет раньше чему буква А (а маленькая ё наоборот ниже чем я).

Кстати вопрос про сравнение строк хорошо задавать на собеседовании, мне кажется, чтобы посмотреть ход мыслей кандидата

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment