В процессе разработки в среде 1С:Предприятие 8 программисты часто сталкиваются с необходимостью сравнения строк, содержащих числовые сегменты, разделенные точками. Типичные примеры таких данных — IP-адреса (например, 245.120.243.567 и 299.125.40.69) или номера версий программного обеспечения (например, 8.3.24.1150 и 8.3.9.100). Попытка сравнить такие строки напрямую стандартными операторами сравнения приводит к логическим ошибкам. Выясним причину этой проблемы и разберем по шагам несколько надежных способов её решения.
Разберем ситуацию на простом примере. Предположим, нам нужно сравнить два IP-адреса: "192.168.1.2" и "192.168.1.10". Очевидно, что второй адрес с точки зрения сетевой логики больше первого. Однако в 1С при выполнении кода:
Если "192.168.1.2" > "192.168.1.10" Тогда
// Этот код выполнится!
КонецЕсли;
Мы получим результат Истина. Платформа сравнивает строки лексикографически (посимвольно слева направо). Дойдя до 11-го символа, система сравнивает символ "2" с символом "1". Поскольку код символа «два» больше кода символа «один», первая строка признается большей, игнорируя дальнейшие знаки.
Попытка просто удалить точки из строки с помощью функции СтрЗаменить(), как предлагалось в некоторых сообщениях форума, также является ошибочной. В таком случае сравнение строки "222.22" (после удаления точек — 22222) и строки "222.022" (после удаления точек — 222022) покажет, что второе число больше, хотя математически это не так. Рассмотрим правильные подходы к решению этой задачи.
Один из самых простых и красивых способов сравнения таких строк — приведение каждого числового сегмента между точками к фиксированной длине (например, до 3 символов) путем добавления ведущих нулей. После такой нормализации строки можно сравнивать стандартным оператором > или <.
Проанализируем алгоритм реализации этого метода:
Посмотрим на практический пример реализации этого метода на языке 1С:
&НаКлиенте
Процедура СравнитьАдресаМетодомНормализации()
IPСтрока1 = "245.120.243.567";
IPСтрока2 = "299.125.40.69";
НормализованныйIP1 = НормализоватьСтрокуIP(IPСтрока1);
НормализованныйIP2 = НормализоватьСтрокуIP(IPСтрока2);
Если НормализованныйIP1 > НормализованныйIP2 Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Первый IP-адрес больше");
ИначеЕсли НормализованныйIP1 < НормализованныйIP2 Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Второй IP-адрес больше");
Иначе
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Адреса равны");
КонецЕсли;
КонецПроцедуры
&НаКлиентеБезКонтекста
Функция НормализоватьСтрокуIP(Знач ИсходныйАдрес)
ЧастиАдреса = СтрРазделить(ИсходныйАдрес, ".");
Для Индекс = 0 По ЧастиАдреса.ВГраница() Цикл
// Приводим к числу и форматируем, задавая фиксированную ширину 3 символа с ведущими нулями
ЧисловоеЗначение = Число(ЧастиАдреса[Индекс]);
ЧастиАдреса[Индекс] = Формат(ЧисловоеЗначение, "ЧЦ=3; ЧВН=; ЧГ=0");
КонецЦикла;
Возврат СтрСоединить(ЧастиАдреса, ".");
КонецФункции
После нормализации наши строки примут вид "245.120.243.567" и "299.125.040.069". Сравнение таких строк вернет абсолютно корректный результат.
Этот способ основан на разбиении обеих строк на массивы и поочередном сравнении элементов с одинаковыми индексами как чисел. Мы прекращаем сравнение, как только находим первый неравный элемент.
Разберем по шагам код реализации покомпонентного сравнения:
&НаКлиентеБезКонтекста
Функция СравнитьIPПокомпонентно(Знач IP1, Знач IP2)
Массив1 = СтрРазделить(IP1, ".");
Массив2 = СтрРазделить(IP2, ".");
// Определяем минимальное количество итераций на случай разного числа сегментов
КоличествоСегментов = Мин(Массив1.Количество(), Массив2.Количество());
Для Индекс = 0 По КоличествоСегментов - 1 Цикл
Число1 = Число(Массив1[Индекс]);
Число2 = Число(Массив2[Индекс]);
Если Число1 > Число2 Тогда
Возврат 1; // Первая строка больше
ИначеЕсли Число1 < Число2 Тогда
Возврат -1; // Вторая строка больше
КонецЕсли;
КонецЦикла;
// Если общие сегменты равны, больше та строка, у которой сегментов больше
Если Массив1.Количество() > Массив2.Количество() Тогда
Возврат 1;
ИначеЕсли Массив1.Количество() < Массив2.Количество() Тогда
Return -1;
КонецЕсли;
Возврат 0; // Строки полностью идентичны
КонецФункции
Если в вашей конфигурации внедрена Библиотека стандартных подсистем (БСП), вам вообще не требуется писать собственный программный код для разбора строк. В составе БСП присутствует готовая и протестированная функция, оптимизированная для клиент-серверного использования.
Рассмотрим применение стандартной функции общего модуля:
РезультатСравнения = ОбщегоНазначенияКлиентСервер.СравнитьВерсии("299.125.40.69", "245.120.243.567");
Если РезультатСравнения > 0 Тогда
// Первая строка больше
ИначеЕсли РезультатСравнения < 0 Тогда
// Вторая строка больше
Иначе
// Строки эквивалентны
КонецЕсли;
Данный метод идеально подходит для сравнения строк, разделенных точкой, и гарантирует надежную работу в любых современных конфигурациях 1С.
Если на входе гарантированно присутствуют корректные IPv4-адреса (в которых каждое число находится в диапазоне от 0 до 255), наиболее производительным решением будет их перевод в единое 32-битное числовое значение.
Математическая формула перевода выглядит следующим образом:
Число = (Октет1 * 16777216) + (Октет2 * 65536) + (Октет3 * 256) + Октет4
Реализуем данный математический подход в коде 1С:
&НаКлиентеБезКонтекста
Функция IPАдресВЧисло(Знач СтрокаIP)
Части = СтрРазделить(СтрокаIP, ".");
Если Части.Количество() <> 4 Тогда
Возврат 0; // Возвращаем 0 при неверном формате
КонецЕсли;
ЧисловойАдрес = Число(Части[0]) * 16777216
+ Число(Части[1]) * 65536
+ Число(Части[2]) * 256
+ Число(Части[3]);
Возврат ЧисловойАдрес;
КонецФункции
Этот метод незаменим, когда требуется выполнять частые операции сравнения или определять вхождение IP-адреса в заданный диапазон (например, принадлежит ли адрес определенной подсети).
Стоит отметить, что внутри запросов 1С невозможно использовать строковые функции разбора (такие как СтрРазделить) или обращаться к методам БСП. Если перед вами стоит задача выполнять сортировку или сравнение IP-адресов в динамических списках на управляемых формах или в отчетах СКД, рекомендуется использовать денормализацию данных при записи.
При записи элемента справочника или документа преобразуйте строковый IP-адрес в число (с помощью Способа 4) или в нормализованную строку с ведущими нулями (с помощью Способа 1) и записывайте это значение в отдельный реквизит базы данных (например, IPАдресЧислом или IPАдресНормализованный). В этом случае СУБД сможет мгновенно сортировать и сравнивать данные реквизиты стандартными методами платформы.