Проверка корректности расстановки скобок в текстовой строке — это классическая задача программирования, которая часто встречается при написании парсеров, синтаксических анализаторов или в качестве тестового задания. В рамках платформы 1С:Предприятие мы можем решить эту задачу несколькими способами: от простого обхода строки со счетчиком до использования стека. Давайте подробно разберем, как реализовать надежный алгоритм проверки, который будет возвращать результат «правильно» или «неправильно».
Часто начинающие разработчики предлагают простое решение: посчитать общее количество открывающих скобок и сравнить его с количеством закрывающих. На первый взгляд это кажется логичным, но давайте проанализируем ситуацию на примере строки ")(" или "())(". В обоих случаях количество символов ( и ) совпадает, однако расстановка явно ошибочна. Закрывающая скобка не может идти раньше открывающей.
Следовательно, нам необходимо учитывать не только итоговое количество, но и порядок их следования. Рассмотрим алгоритм, который позволяет сделать это за один проход по строке.
Если в условии задачи сказано, что выражение содержит только один тип скобок (в нашем случае — круглые), то самым оптимальным решением будет использование числового счетчика («баланса»). Выясним принцип его работы:
(, увеличиваем счетчик на 1.), уменьшаем счетчик на 1.Давайте реализуем этот алгоритм на языке 1С, учитывая требования к возвращаемому результату («правильно» или «неправильно»):
Функция ПроверитьКорректностьСкобок(Знач ТекстВыражения) Экспорт
БалансСкобок = 0;
// Перебираем каждый символ строки
Для Позиция = 1 По СтрДлина(ТекстВыражения) Цикл
Символ = Сред(ТекстВыражения, Позиция, 1);
Если Символ = "(" Тогда
БалансСкобок = БалансСкобок + 1;
ИначеЕсли Символ = ")" Тогда
БалансСкобок = БалансСкобок - 1;
КонецЕсли;
// Если закрывающих стало больше, чем открытых на данный момент
Если БалансСкобок < 0 Тогда
Возврат "неправильно";
КонецЕсли;
КонецЦикла;
// Если после прохода баланс нулевой - все хорошо
Если БалансСкобок = 0 Тогда
Возврат "правильно";
Иначе
Возврат "неправильно";
КонецЕсли;
КонецФункции
В приведенном выше коде мы использовали функцию Сред() для извлечения символа. Стоит отметить, что в 1С нет встроенного итератора для строк типа Для каждого Символ Из Строка, поэтому использование цикла Для по индексам с функцией Сред() является стандартным и наиболее понятным подходом.
Проанализируем производительность: данный алгоритм имеет сложность O(n), где n — длина строки. Это означает, что он работает максимально быстро, проходя по тексту всего один раз. Для большинства арифметических выражений в 1С этого более чем достаточно.
Если задача усложняется и нам нужно проверять не только круглые, но и квадратные [] или фигурные {} скобки, метод счетчика перестает работать. Например, выражение "([)]" даст нулевой баланс по обоим типам, но оно некорректно, так как нарушена вложенность.
Для решения таких задач мы применим структуру данных Стек. В 1С роль стека отлично выполнит объект Массив.
Рассмотрим шаги алгоритма:
(, [, {) мы добавляем ее в массив (в конец).), ], }) мы проверяем последний элемент массива.], а последняя была (), то выражение неверно.Посмотрим на пример реализации такого универсального парсера:
Функция ПроверитьВсеТипыСкобок(Знач ТекстВыражения) Экспорт
СтекСкобок = Новый Массив;
Для к = 1 По СтрДлина(ТекстВыражения) Цикл
ТекущийСимвол = Сред(ТекстВыражения, к, 1);
// Добавляем открывающие в стек
Если ТекущийСимвол = "(" Или ТекущийСимвол = "[" Или ТекущийСимвол = "{" Тогда
СтекСкобок.Добавить(ТекущийСимвол);
// Обрабатываем закрывающие
ИначеЕсли ТекущийСимвол = ")" Или ТекущийСимвол = "]" Или ТекущийСимвол = "}" Тогда
Если СтекСкобок.Количество() = 0 Тогда
Возврат "неправильно"; // Закрывающая без открывающей
КонецЕсли;
ПоследняяОткрытая = СтекСкобок[СтекСкобок.Количество() - 1];
// Проверяем соответствие пар
Если (ТекущийСимвол = ")" И ПоследняяОткрытая = "(")
Или (ТекущийСимвол = "]" И ПоследняяОткрытая = "[")
Или (ТекущийСимвол = "}" И ПоследняяОткрытая = "{") Тогда
СтекСкобок.Удалить(СтекСкобок.Количество() - 1);
Иначе
Возврат "неправильно"; // Не тот тип скобки
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат ?(СтекСкобок.Количество() = 0, "правильно", "неправильно");
КонецФункции
Некоторые участники обсуждения предложили использовать встроенную функцию Вычислить(). Рассмотрим этот вариант. Действительно, платформа сама умеет проверять синтаксис при вычислении выражений.
Преимущество: Не нужно писать свой парсер, 1С проверит не только скобки, но и правильность написания функций.
Недостаток: Если в строке содержатся данные, которые 1С не может вычислить (например, переменные, которых нет в контексте, или просто произвольный текст), функция Вычислить() вызовет исключение, даже если скобки расставлены верно.
Поэтому метод с Попытка...Исключение и Вычислить() подходит только в том случае, если вы уверены, что внутри скобок находится валидный код на языке 1С. Для общей задачи проверки текста (например, математической формулы или текста в чате) лучше использовать описанный выше посимвольный обход.
Подведем итог нашего анализа. Для проверки простого выражения с круглыми скобками достаточно завести одну переменную БалансСкобок. Это самое быстрое и экономное по памяти решение. Если же вы работаете со сложными вложенными конструкциями разных типов, ваш выбор — массив, работающий по принципу стека. Помните, что проверка только общего количества вхождений символов — это распространенная ошибка, которой стоит избегать.