Почему похожие строки не совпадают

На вид строки одинаковы, но отличаются буквой, пробелом или знаком.

Почему похожие строки не совпадают

Найти причину можно без изучения всей таблицы Unicode. Сначала определите строку, которая не совпала, затем проверьте подозрительную букву, внутренний пробел или букву из нескольких частей и исправьте текст по правилам конкретного поля.

В статье вы узнаете:

Артикулы A-120 и А-120 выглядят одинаково, но это разные строки

В старой выгрузке магазина есть артикул A-120. В новой выгрузке находится с виду тот же А-120, но сравнение двух списков помещает первую запись в блок «Только в первом списке», а вторую в блок «Только во втором списке». Выбран режим «Не учитывать регистр», пробелов по краям нет. Кажется, калькулятор упрямится.

Разница спрятана в первой букве. В старом артикуле стоит латинская A, в новом кириллическая А. У каждой есть собственный номер в таблице символов Unicode: латинская называется U+0041, кириллическая U+0410. Запись U+ просто сообщает номер знака. В выбранном шрифте рисунки почти совпали, но для программы это два разных элемента.

Проверка занимает несколько секунд. Если вставить латинскую A в конвертер ASCII-кодов, получится число 65. Кириллическая А в эту базовую таблицу латинских знаков не входит, поэтому инструмент остановится и назовёт U+0410.

Если правило артикулов разрешает только латинские буквы, новый вариант нужно исправить и повторить сравнение. В обычном имени или русском предложении та же кириллическая «А» совершенно правильна. Ошибка находится не в букве, а в её несоответствии договорённости для конкретного поля.

Похожая буква, другой пробел и составная «ё» создают три разные проблемы

Буквы из разных алфавитов. Латинские A, B, C, E, H, K, M, O, P, T, X похожи на некоторые заглавные буквы кириллицы. Список здесь нужен только как предупреждение, а не как команда заменять всё подряд. Латинское P в коде товара может быть обязательным, а кириллическая «Р» в фамилии Романов должна остаться кириллической.

Пробелы с разными свойствами. Обычный пробел имеет номер U+0020. Неразрывный пробел U+00A0 выглядит примерно так же, но не разрешает разрывать строку между соседними словами. Фразы Иван Петров и Иван Петров содержат по 11 видимых позиций, поэтому простой взгляд и даже длина строки не всегда помогают. URL-кодировщик покажет обычный пробел как %20, а неразрывный как %C2%A0.

Многие программы автоматически убирают пустое место в начале и конце строки, но сохраняют знак между словами. Копирование из сайта, PDF или оформленного документа способно принести неразрывный пробел вместе с текстом.

Одна буква из нескольких частей. Русская «ё» может храниться готовым знаком U+0451. Другой вариант состоит из буквы «е» U+0435 и отдельного знака двух точек U+0308. Браузер рисует обе части в одном месте, поэтому человек видит одну «ё», а простое сравнение получает одну часть в первой строке и две во второй.

Счётчик символов покажет для готовой «ё» длину 1, а для сочетания «е» с отдельными точками длину 2, потому что текущий инструмент считает технические части, которыми браузер хранит строку. URL-кодировщик тоже выдаст разные записи: %D1%91 и %D0%B5%CC%88. Это не две орфографии слова. Это два способа сохранить одну и ту же букву.

Нормализация склеивает эквивалентные формы, но не похожие алфавиты

Нормализация Unicode приводит эквивалентные последовательности к согласованной форме. Один распространённый вариант называется NFC. Для нашего примера он соберёт е и отдельные две точки в готовую ё. После NFC обе строки получат одинаковую внутреннюю запись и смогут совпасть при обычном сравнении.

Такое преобразование описывает Unicode Standard Annex #15. Стандарт задаёт четыре формы нормализации. Для обычного хранения и сравнения текста часто выбирают NFC, но форму должен определять разработчик системы. Пользователю важнее практический вывод: одинаковые буквы, собранные разными способами, можно привести к одному виду без ручного перебора.

Латинская A и кириллическая А нормализацией не склеятся. Они обозначают буквы разных письменностей и могут иметь разные правила регистра, произношение и назначение. То же относится к латинской C и кириллической «С». Похожий рисунок не делает знаки эквивалентными.

Регистр тоже обрабатывается отдельно. Строки Box и box могут считаться равными в списке имён и разными в техническом ключе. Удаление пробелов по краям является ещё одним отдельным решением. Одна команда «почистить текст» на самом деле скрывает несколько правил, и каждое нужно выбирать по смыслу данных.

Найдите сначала плохую строку, потом плохой знак

Для бытовой сверки не нужен экран, заполненный шестнадцатеричными числами. Достаточно идти от общего различия к конкретному месту:

  1. Вставьте старые и новые значения в сравнение списков. Одна запись должна занимать одну строку.
  2. Сначала оставьте режим без учёта регистра. Если различие исчезло, причина была в заглавных и строчных буквах. Затем включите строгий режим, если регистр важен для ваших данных.
  3. Если похожие строки всё ещё разнесены, сравните их длину в счётчике символов. Лишняя позиция подскажет, что появился пробел, невидимый знак или составная буква. Одинаковая длина проблему не исключает: A и А обе занимают одну позицию.
  4. Для артикула, команды или другого значения, где ожидается только базовая латиница, вставляйте подозрительные знаки по одному в конвертер ASCII. Он назовёт первую букву, которая не входит в диапазон 0-127.
  5. Если подозрение падает на пробел, кириллицу или составной знак, закодируйте обе строки как компоненты в URL-кодировщике. Разные последовательности %XX подтвердят, что внутри находятся разные байты UTF-8.
  6. Исправьте источник строки, снова запустите сравнение и сохраните исходный вариант отдельно, пока не убедитесь, что замена верна.

Этот путь показывает класс проблемы, но не рисует точный посимвольный diff. В длинной строке после первого подтверждения удобнее включить показ невидимых знаков в редакторе или использовать Unicode-инспектор, который выводит номер каждого символа. Не пытайтесь искать отличие, меняя буквы наугад: так легко починить артикул и одновременно испортить соседнее имя.

Правило очистки зависит от того, что хранится в поле

Для артикула A-120 можно установить простой договор: латинские буквы A-Z, цифры и дефис. Тогда кириллическая А отклоняется при вводе, а человек видит понятное сообщение. Это надёжнее, чем принять любой знак, а потом незаметно угадать, что он «наверное имел в виду».

С именами и обычным текстом подход другой. Кириллица, латиница, дефисы и знаки с диакритикой могут быть правильной частью записи. Здесь разумно сохранить оригинал, привести равнозначные составные буквы к NFC и отдельно договориться о пробелах. Автоматически переводить всё в один алфавит нельзя.

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

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

Иногда несовпадение защищает от подмены

Похожие знаки используют не только по ошибке. Строка paypal может быть записана с кириллическими «а», и в некоторых шрифтах подмена почти незаметна. Стандарт Unicode UTS #39 называет такие строки смешиваемыми и приводит именно пару с латинским и смешанным написанием paypal как пример.

Таблица похожих символов помогает заметить риск, но сам стандарт предупреждает: её нельзя использовать как автоматическую нормализацию идентификаторов. Если программа молча превратит любой похожий знак в латинский, она может склеить разные имена и скрыть подмену вместо того, чтобы показать её.

Смешение алфавитов не доказывает обман. В названии, научной записи или многоязычном тексте оно бывает законным. Но в адресе сайта, логине, артикуле и другом коротком идентификаторе неожиданная буква из другого алфавита является поводом остановиться и проверить источник.

Практическое правило получается скучным, зато рабочим. Для свободного текста сохраняйте буквы и нормализуйте только эквивалентные формы по заранее выбранному правилу. Для идентификаторов задавайте допустимый алфавит и отклоняйте неожиданное. Если две строки вдруг не совпали, не ругайте калькулятор: возможно, он первым заметил букву, которую глаз вежливо пропустил.

Есть что добавить?

Напишите своё мнение, комментарий или предложение.