Поиск, замена и извлечение фрагментов строк
Пожалуй, наиболее востребованными операциями при работе со строками являются поиск и замена фрагментов, а также их извлечение. В данном разделе мы изучим функции MQL5 API, которые помогут решить эти задачи. Примеры их использования сведены в файл StringFindReplace.mq5.
int StringFind(string value, string wanted, int start = 0)
Функция выполняет поиск подстроки wanted в строке value, начиная с позиции start. Если подстрока найдена, функция вернет позицию, где она начинается, причем символы в строке нумеруются с 0. В противном случае функция вернет -1. Оба параметра передаются по значению, что позволяет обрабатывать не только переменные, но и промежуточные результаты вычислений (выражения, вызовы функций).
Поиск выполняется на строгое соответствие символов, то есть с учетом регистра. Если требуется искать без учета регистра, необходимо предварительно привести исходную строку к единому регистру посредством StringToLower или StringToUpper.
Попробуем подсчитать количество вхождений искомой подстроки в тексте с помощью StringFind. Для этого напишем вспомогательную функцию CountSubstring, которая будет в цикле вызывать StringFind, постепенно смещая стартовую позицию поиска в последнем параметре start. Цикл продолжается пока находятся новые вхождения подстроки.
int CountSubstring(const string value, const string wanted)
|
Важно отметить, что представленная реализация ищет подстроки с возможностью наложения. Это происходит из-за того, что текущая позиция изменяется на 1 (++cursor) перед поиском следующего вхождения. В результате, при поиске, например, подстроки "AAA" в строке "AAAAA" будет найдено 3 совпадения. Технические требования на поиск могут отличаться от данного поведения. В частности, существует практика продолжать поиск после той позиции, где закончился предыдущий найденный фрагмент. В таком случае потребуется модифицировать алгоритм, чтобы курсор сдвигался с шагом, равным StringLen(wanted).
В функции OnStart вызовем CountSubstring для разных аргументов.
void OnStart()
|
int StringReplace(string &variable, const string wanted, const string replacement)
Функция заменяет в строке variable все найденные подстроки wanted на другую подстроку replacement.
Функция возвращает количество сделанных замен или -1 в случае ошибки. Код ошибки можно получить, вызвав функцию GetLastError. В частности, возможны ошибки нехватки памяти или использования в качестве аргумента неинициализированной строки (NULL). Параметры variable и wanted должны быть строками ненулевой длины.
Когда в качестве аргумента replacement задается пустая строка "", все вхождения wanted просто вырезаются из исходной строки.
Если замен не было, результат функции равен 0.
Продолжая пример StringFindReplace.mq5, проверим StringReplace в действии.
string abracadabra = "ABRACADABRA";
|
Далее попробуем выполнить с помощью функции StringReplace одну из задач, встречающуюся при обработке произвольных текстов, а именно: требуется обеспечить, чтобы некоторый символ разделитель всегда использовался одиночным, то есть последовательности нескольких таких символов должны быть заменены на один. Обычно речь идет о пробелах между словами, но в технических данных могут быть и другие разделители. Мы для наглядности будем тестировать свою программу на разделителе '-'.
Реализуем алгоритм в виде отдельной функции NormalizeSeparatorsByReplace:
int NormalizeSeparatorsByReplace(string &value, const ushort separator = ' ')
|
В цикле do-while программа пытается заменить последовательность из двух разделителей на один, и цикл продолжается, пока функция StringReplace возвращает значения больше 0 (то есть, заменять еще есть что). Функция возвращает общее количество сделанных замен.
В функции OnStart "очистим" нашу надпись от множественных символов '-'.
...
|
int StringSplit(const string value, const ushort separator, string &result[])
Функция разбивает переданную строку value на подстроки по заданному разделителю и помещает их в массив result. Функция возвращает количество полученных подстрок или -1 в случае ошибки.
Если разделителя в строке нет, в массиве будет один элемент, равный всей строке целиком.
Если исходная строка пуста или равна NULL, функция вернет 0.
Для демонстрации работы данной функции решим предыдущую задачу новым способом — с помощью StringSplit. Для этого напишем функцию NormalizeSeparatorsBySplit.
int NormalizeSeparatorsBySplit(string &value, const ushort separator = ' ')
|
Когда разделители встречаются в исходном тексте один за другим, соответствующий элемент в выходном массиве StringSplit оказывается пустой строкой "". Также пустая строка будет в начале массива, если текст начинается с разделителя, и в конце массива, если текст заканчивается разделителем.
Чтобы получить "очищенный" текст, нужно сложить все непустые строки из массива, "склеив" их одиночными символами-разделителями. Причем в разделитель следует "превращать" только те пустые элементы, у которых предыдущий элемент массива не является также пустым.
Разумеется, это лишь один из возможных вариантов реализации данного функционала. Проверим его в функции OnStart.
...
|
string StringSubstr(string value, int start, int length = -1)
Функция извлекает из переданного текста value подстроку, начинающуюся с указанной позиции start и длиной length. Начальная позиция может быть от 0 до длины строки минус 1. Если длина length равна -1 или превышает количество символов от start до конца строки, будет извлечена вся оставшаяся часть строки.
Функция возвращает подстроку или пустую строку, если параметры заданы неверно.
Посмотрим, как это работает.
PRT(StringSubstr("ABRACADABRA", 4, 3)); // 'CAD'
|