Что нового

[RegExp] Можно ли узнать позицию начала совпадения регулярного выражения в строке?

Крепыш

Новичок
Сообщения
105
Репутация
2
Есть такое регулярное выражение:
Код:
Local $aRes = StringRegExp(FileRead($hFile), '(Дата;[\d\.;]*?)\r\n(Время;[\d:;]*?)\r\n(Ещё чего-то;[\d\.;]*?)$', 1)

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

Ksaan

Знающий
Сообщения
207
Репутация
15
Вроде так должно работать
Код:
$sChars = FileRead($hFile)
Local $aRes = StringRegExp($sChars, '(Дата;[\d\.;]*?)\r\n(Время;[\d:;]*?)\r\n(Ещё чего-то;[\d\.;]*?)$', 1)
$sResult = StringInStr($hFile, $aRes[0])
MsgBox(0, "позиция:", $sResult)
 
Автор
К

Крепыш

Новичок
Сообщения
105
Репутация
2
Файл имеет вид:
Код:
Дата;...;20.02.2015;21.02.2015;22.02.2015
Время;...;17:18;18:20;20:47
Ещё чего-то;...
Дата;23.02.2015
Время;...
Ещё чего-то;...
В строке одна неделя.

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

А почему сразу не сделать замену?
Код:
StringRegExpReplace
Как вариант, можно. Но,[list type=decimal]
[*]во-первых, тогда нужно будет переписывать весь файл полностью;
[*]во-вторых, замена делается только при условии, что продолжается та неделя, которая уже содержится в файле. На самом деле нужна не замена, а дозапись в файл, но, поскольку дописывать нужно в несколько строк, то приходится делать путём замены.
Если начинается новая неделя, то курсор переводится в конец файла FileSetPos($hFile, 0, 2), и данные записываются в новые строки.[/list]


Добавлено:
Сообщение автоматически объединено:

Ksaan сказал(а):
Вроде так должно работать
Код:
$sChars = FileRead($hFile)
Local $aRes = StringRegExp($sChars, '(Дата;[\d\.;]*?)\r\n(Время;[\d:;]*?)\r\n(Ещё чего-то;[\d\.;]*?)$', 1)
$sResult = StringInStr($hFile, $aRes[0])
MsgBox(0, "позиция:", $sResult)

Спасибо, сделал так:
Код:
Local $hFile = FileOpen($sFile, 1)
FileSetPos($hFile, 0, 0)
$sFile = FileRead($hFile)
Local $aRes = StringRegExp($sFile, '(Дата;[\d\.;]*?)\r\n(Время;[\d:;]*?)\r\n(Ещё чего-то;[\d\.;]*?)$', 1)
FileSetPos($hFile, StringInStr($sFile, $aRes[0])-1, 0)
Вроде, работает.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
Крепыш
@extended - возвращает позицию конца. Выполните StringLen для найденного образца и отнимите это значение от @extended. Это должно работать быстрее чем StringInStr. Пример к функции StringRegExp в русской справке содержит в том числе и определение начала найденного образца.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Мне вот интересно как найти позицию найденного, если использовать флаг 3 (Return array of global matches)? :scratch:
Конечно можно использовать 1 и прокручивать в цикле, но почему не предусмотрено тоже самое для глобального поиска...
 

asdf8

Скриптер
Сообщения
564
Репутация
152
CreatoR сказал(а):
Мне вот интересно как найти позицию найденного, если использовать флаг 3 (Return array of global matches)? :scratch:
Конечно можно использовать 1 и прокручивать в цикле, но почему не предусмотрено тоже самое для глобального поиска...

Больше года назад я предлагал сделать это, но, что-то никакой реакции.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
asdf8
А если попробовать сделать функцию _StringRegExp2 используя цикл с параметром 1 для StringRegExp и сравнить скорость работы с оригиналом StringRegExp с параметром 3. Если разница небольшая то в общем проблема окажется решена предоставленными возможностями.
 

asdf8

Скриптер
Сообщения
564
Репутация
152
AZJIO
Разница в скорости циклов AutoIt и PCRE просто огромная. К тому-же PCRE ищет сначала позиции вхождения текста, поэтому, реализовать это было бы не сложно.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
asdf8
Реализовать не сложно, но это очередное изменение формата и поломка скриптов. Оно иногда надо, иногда не надо, более мягкий вариант добавление нового режима 5, 6 и т.д., чтобы не ломать существующие. А движок PCRE теоретически много чего может выдать, мало ли что ещё захочется через 5 минут. Вот в Python'e через методы можно получить:
1. начало/конец найденного образца целиком
2. начало/конец каждой группы регулярного выражения
3. Группы по индексу, по имени, по тегу первый/последний
4. И другое завязанное уже с Python: возвращение словаря, кортежа

Разница в скорости циклов AutoIt и PCRE просто огромная
Сам цикл выполняется быстро, пустой выполняет миллион операций за десятые доли секунды. Движок регулярных выражений оптимизировали, кэширует строку регулярного выражения, хотя этот критерий вроде не сильно помогает, ведь он сравнивает строку с предыдущей использованной строкой чтобы не компилировать регулярное выражение, конечно тут бы добавить функцию компиляции и тогда задача бы решилась навсегда. И добавилось что обработка данных функцией и нативный формат строк теперь одинаковый и поиск пошаговый вызывает меньше тормозов. То есть с момента появления новой версии AutoIt скорость режимов регулярный выражений нужно тестировать заново чтобы дать оценку.
 
Автор
К

Крепыш

Новичок
Сообщения
105
Репутация
2
AZJIO сказал(а):
@extended - возвращает позицию конца. Выполните StringLen для найденного образца и отнимите это значение от @extended. Это должно работать быстрее чем StringInStr.
У меня в StringRegExp шаблон для поиска содержит 5 групп. Не думаю, что определение и суммирование длин каждой группы будет быстрее, чем StringInStr.
 

asdf8

Скриптер
Сообщения
564
Репутация
152
AZJIO [?]
Оно иногда надо, иногда не надо, более мягкий вариант добавление нового режима 5, 6 и т.д., чтобы не ломать существующие.
Я ж это и предлагал.

Движок регулярных выражений оптимизировали, кэширует строку регулярного выражения ...
На скорость циклов AutoIt это не повлияло.

тут бы добавить функцию компиляции и тогда задача бы решилась навсегда.
Было бы не полохо, но, по моим наблюдениям, скорость выполнения увеличится на величину порядка одного- двух процентов.
 

C2H5OH

AutoIT Гуру
Сообщения
1,473
Репутация
333
Не думаю, что определение и суммирование длин каждой группы будет быстрее, чем StringInStr.

будет, imho.

Код:
$sChars = FileRead($hFile)
Local $aRes = StringRegExp($sChars, '(Дата;[\d\.;]*?)\r\n(Время;[\d:;]*?)\r\n(Ещё чего-то;[\d\.;]*?)$', 1)
If @error Then
   MsgBox(0, "позиция:", "нет совпадений")
Else
   $sResult = @extended
   $sResult -= StringLen($aRes[0])
   MsgBox(0, "позиция:", $sResult)
EndIf
 
Автор
К

Крепыш

Новичок
Сообщения
105
Репутация
2
C2H5OH сказал(а):
Не думаю, что определение и суммирование длин каждой группы будет быстрее, чем StringInStr.

будет, imho.
Проверяем:
Код:
Local $sText = FileRead($hFile)
Local $aRes = StringRegExp($sText, '(Дата;[\d\.;]*?)\r\n(Время;[\d:;]*?)\r\n(Ещё чего-то;[\d\.;]*?)$', 1)
ConsoleWrite(f1($aRes, @extended) & @CRLF)
ConsoleWrite(f2($sText, $aRes) & @CRLF)
ConsoleWrite('StringLen: ' & _Test1($aRes, @extended) & @CRLF)
ConsoleWrite('StringInStr: ' & _Test2($sText, $aRes) & @CRLF)

Func f1($aRes, $iEnd)
  Local $sResult = $iEnd
  For $i = 0 To UBound($aRes)-1
     $sResult -= StringLen($aRes[$i])
  Next
  Return $sResult-4
EndFunc

Func f2($sText, $aRes)
  Return StringInStr($sText, $aRes[0], 1, -1)
EndFunc

Func _Test1($aRes, $iEnd)
  Local $t = TimerInit()
  For $i = 1 To 100000
    f1($aRes, $iEnd)
  Next
  Return TimerDiff($t)
EndFunc

Func _Test2($sText, $aRes)
  Local $t = TimerInit()
  For $i = 1 To 100000
    f2($sText, $aRes)
  Next
  Return TimerDiff($t)
EndFunc

Кстати, как попроще написать функцию тестирования? Чтоб была одна функция _Test, типа такой:
Код:
Func _Test(f, $v1, $v2)
  Local $t = TimerInit()
  For $i = 1 To 100000
    f($v1, $v2)
  Next
  Return TimerDiff($t)
EndFunc

Результат:
Код:
StringLen: 928.410656413941
StringInStr: 533.278990727546
StringInStr почти в 2 раза быстрее.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
Крепыш
Чисто логически: StringInStr просматривает файл (строку) от начала до конца, то есть перебирает каждый символ и сравнивает его с первым символом искомого и так двигается к следующему символу. Функция StringLen явно не сверяет каждую букву, а просто переходит к следующей, увеличивая счётчик на 1. Длина найденного по определению короче, чем строка, в которой оно ищется. Итак худший случай для StringLen: короткая строка в которой выполняется поиск и захват наибольшего, то есть всей строки, при этом функции должны выдать близки результат. Лучший вариант для StringLen; искомая строка размером несколько мегабайт, а найденный шаблон несколько байт. В этом случает StringInStr будет перебирать миллионы символов перед тем как найти нужное и хуже всего, если он окажется в конце строки, а StringLen сделает проход по нескольким байтам и отличие этих алгоритмических ходов будет равно процентным отличием размера количества пройденных байтов, то есть миллион против 10.

На счёт вашего теста, сделайте его для начала правильным. Зачем вы StringLen делаете в цикле? Вы предыдущий пост не читали?

asdf8
Было бы не полохо, но, по моим наблюдениям, скорость выполнения увеличится на величину порядка одного- двух процентов.
Зависит от конкретных условий. Если обработка мегабайта данных то конечно процесс компиляции несоизмеримо мал к прочессу обрабатываемых данных. А если взять в цикле пару регулярных выражений, которые обновляют кэш и требуют перекомпиляции на каждом шаге, а размер обрабатываемой строки несколько десятков символов, например обработка имён файлов в цикле. И так что такое компиляция в моём представлении (наверно нужно Google сначала, но...): первоначальная обработка шаблона на ошибки, выявления групп, раскрытия метасимволов, например \d заменяется на числовые диапазоны входящих в группу цифр и т.д. чтобы потом просто проверить вхождение символа в этот диапазон. Это тоже как анализатор подсветки чтобы определить код или интерпретатор чтобы определить ключевые слова. И компиляция зависит от размера и количества групп. В любом случае проценты составляют величину обработки строки регулярного выражения к обрабатываемой строке. Если обрабатываемая строка мала, а шаблон сложный то процент уже будет не 1 и 2, а как бы не 50. Это гипотетические мысли вслух, конечно же всё познаётся в тесте.
 

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Я проверял эту компиляцию, думал что будет быстрее работать, но нет, существенной разницы не заметил. Что делал. Был массив html кодов, нужно в цикле пройтись и получить 5 значений разными регулярками. Сделал примерно следующее:
Код:
For $html In $html_array
	$reg_exp = StringRegExp('...', 'a')
	$data_a = $reg_exp[0]
Next
For $html In $html_array
	$reg_exp = StringRegExp('...', 'b')
	$data_b = $reg_exp[0]
Next
For $html In $html_array
	$reg_exp = StringRegExp('...', 'c')
	$data_c = $reg_exp[0]
Next
For $html In $html_array
	$reg_exp = StringRegExp('...', 'd')
	$data_d = $reg_exp[0]
Next
For $html In $html_array
	$reg_exp = StringRegExp('...', 'e')
	$data_e = $reg_exp[0]
Next

Я надеялся что, за счет компиляции такое решение с 5 циклами будет быстрее чем 1 цикл но с 5 регулярками. Но результаты теста показали, что выигрыш в скорости был в пределах погрешности.
 
Автор
К

Крепыш

Новичок
Сообщения
105
Репутация
2
AZJIO сказал(а):
Зачем вы StringLen делаете в цикле?
Затем, что вычитать надо сумму длин всех групп, а не только первой, а также сумму длин символов, не вошедших в группы.
Теперь насчёт теста возражений нет?

AZJIO сказал(а):
Чисто логически: StringInStr просматривает файл (строку) от начала до конца
AZJIO сказал(а):
Лучший вариант для StringLen; искомая строка размером несколько мегабайт, а найденный шаблон несколько байт. В этом случает StringInStr будет перебирать миллионы символов перед тем как найти нужное и хуже всего, если он окажется в конце строки

В моём случае, StringInStr как раз ведёт поиск с конца строки (StringInStr($sText, $aRes[0], 1, -1)), если я правильно понял описание параметров из справки:
occurrence [необязательный] Номер искомого вхождения подстроки в строку. Используйте отрицательное значение параметра для поиска справа.
т.к. искомый фрагмент находится в конце строки: символ '$' в шаблоне.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
Крепыш [?]
отняв от @extended длину первой группы?
Если ищется потом позиция, то зачем в шаблоне группы? А без групп флаг 1 возвращает весь найденный текст в первый элемент массива. Если же необходимы группы использовать флаг 2, тоже самое только первый элемент массива весь найденный текст.

В моём случае, StringInStr как раз ведёт поиск с конца строки
да не важно где в данном случае начало поиска. И уточнение не до конца строки а до найденного, а в случае не найденного до конца, но тут оно по условию найдено.


inververs
Я надеялся что, за счет компиляции такое решение с 5 циклами будет быстрее чем 1 цикл но с 5 регулярками. Но результаты теста показали, что выигрыш в скорости был в пределах погрешности.
Компиляция это процесс анализа регулярного выражения зависящий от сложности, а в примере явно задан 1 символ (ни одного метасимвола даже), то процесс компиляции равен получению номера символа "а". Хотя по тестам на оф.сайте декларировали в полтора раза ускорение цикла (хотя при каких условиях).
Во вторых если рег.выр. кешируется, то выполнение сравнения с кешем, тоже является дополнительным бременем. Поэтому если одна рег.выр. в цикле, то проверка шаблона с кешем чуть быстрее чем компиляция, при одной букве проверка этого кеша сделает ту же задержку. А если 2 рег.выр. в цикле то получается худший вариант, движок постоянно сравнивает с кешем и он на каждом шаге меняется и приходится и перекомпилировать и сравнивать кеш и кешировать. Поэтому и хотелось чисто получить дескриптор скомпилированного рег.выр.
 
Автор
К

Крепыш

Новичок
Сообщения
105
Репутация
2
AZJIO сказал(а):
да не важно где в данном случае начало поиска.
Очень даже важно, если начинать поиск с той стороны, где заведомо есть искомый фрагмент.

AZJIO сказал(а):
Если же необходимы группы использовать флаг 2, тоже самое только первый элемент массива весь найденный текст.
А это мысль. Спасибо за подсказку.
Немного подправил: теперь вариант со StringLen на 10-20% быстрее, чем с StringInStr
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
inververs
Вот инфу нашёл здесь https://msdn.microsoft.com/ru-ru/library/gg578045%28v=vs.110%29.aspx
но там регулярные выражения в .NET Framework. Думаю большой разницы нет. Там раздел искать "Интерпретированные и скомпилированные регулярные выражения" и читать 3 абзаца.
В отличие от интерпретированных регулярных выражений, скомпилированные регулярные выражения увеличивают время запуска, но позволяют выполнять отдельные методы поиска совпадения с шаблоном быстрее. В результате выгода по производительности от скомпилированных регулярных выражений увеличивается пропорционально числу вызовов методов регулярных выражений.
если требуется выполнить только 10 вызовов методов поиска совпадения, интерпретированное регулярное выражение дает более высокую производительность. Однако скомпилированное регулярное выражение обеспечивает более высокую производительность при большом числе вызовов (в данном случае 13 000).
Что то число слишком большой 13 000 вызовов требуется, чтобы окупить компиляцию рег.выр.
 
Верх