Швидкий регулярний вираз, який перевіряє кількість появи символів на рядку у Vim?


1

Припустимо, у мене є текстовий файл з обмеженою трубкою. Я підозрюю, що один із стовпців може мати вбудований характер труби ('|'). Я знаю, що у файлі є 8 стовпців, і в кожному рядку повинні бути 8-1 = 7символи труби. Отже, мені потрібно знайти всі рядки, які мають 8 або більше '|' символів.

У наступному регулярному вираженні повинні бути знайдені всі подібні випадки, але для того, щоб повернутись на мій файл запису 200 000:

^\(.*|.*\)\{8,}$

Чи є швидший регулярний вираз, який слід використовувати замість нього При занадто довго , я маю в виду більше , ніж я очікував би - принаймні кілька хвилин. Це не настільки великий файл (200K записів), тому я припускаю, що сам регулярний вираз не просто ефективний.


Деякі приклади даних:

SAMPLE_ID|GROUPS|ADDRESSSTRING|LATITUDE|LONGITUDE|COUNTRYCODE|LANGUAGECODE|ISO_2_LTR_CODE
7304094||Rhein-Galerie;Baden-Württemberg|49.48334|8.45007|DEU|ger|DE
7303851||Steigenberger Insel;Baden-Württemberg|47.69005|9.18812|DEU|ger|DE
7303850||Si-Suites;Baden-Württemberg|48.72309|9.16138|DEU|ger|DE

(Я запускаю gVim на WinXP)


Регекс Кріс Джонсен повертає матчі за 1 секунду. Регекс celebdor повертає матчі за 1 хвилину. Мій оригінальний регулярний вираз повернувся значно пізніше.
drapkin11

Відповіді:


2

Ваш регекс схильний до вторгнення в деяку поведінку O (N ^ 2) механізму зворотного відстеження, який використовується у Vim (та багатьох інших мовах та середовищах).

На щастя, є способи написання еквівалентних виразів, які не викликають зайвого зворотного відстеження. Наприклад:

/^\([^|]*|\)\{8}.*$

Загалом, вам не потрібно відповідати "вісім і більше", оскільки якщо ви вже знаєте, рядок є проблематичним, якщо він має вісім (чи є їх більше, чи ні).

Якщо вам потрібно зіставити весь рядок (наприклад, тому що це частина :sоперації), вам потрібно буде зберегти останню частину ( .*$); якщо ви просто використовуєте регулярний вираз, щоб знайти вісім рядків або більше, тоді ви можете залишити .*$кінець.

Також я раджу лише намагатися відповідати одній «стороні» труби всередині групи, яку ви повторите. Це спрощує як мислення про те, як регулярний вирівнювання відповідає рядкам, так і як сам механізм регулярного виведення виконує (це виключає джерело зворотного відстеження).


Тепер, щоб пояснити трохи про "зворотний трек". Подумайте, у вас є лінія, яка містить вісім символів труби:

aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh

Наступний уривок описує, як механізм регулярного вирівнювання намагається співставити ваше вираження з вищевказаним рядком (я додав додаткове пробіл до рядків регулярних виразів, щоб показати (приблизно), де частини регулярного вираження відповідають символам самого рядка).

Перший .*жадібний і відповідатиме всім до кінця рядка, залишаючи характер труби незмінним.

  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                                            |

Найновіший "стискається" матч відмовляється від біту своєї відповідності та повторює спробу решти регексу. У цьому випадку це відбувається по одному символу одночасно (оскільки .буде відповідати будь-якому одному символу). Це зворотне відстеження триває до тих пір, поки інший вираз не зможе збігатися (або поки він не повернеться до початку - це єдиний спосіб, коли він знає, що рядок не відповідає виразу!).

  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                                     |.*    )(.*|

Отже, перший .*відступив достатньо, щоб залишити решту групи, але для другої групи нічого не було. Час відступити ще.

  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                              |.*           )(.*|

Зворотний трек знайшов нову точку зупинки, але тепер друга .*в першій групі проводить свою жадібну відповідність. Друга група не відповідає. .*Починається зворотний трек у другої групи.

  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                              |.*)(.*|.*    )(.*|

Друга група знайшла збіг, але третя група не відповідала. Знову зворотний трек, починаючи з останнього матчу. Друга .*з другої групи відступає назад ні до чого. Перший .*з другої групи відступає ні до чого. Друга .*з першої групи відступає ні до чого. Перший .*з перших групових успішно відкликав.

  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                       |.*                  )(.*|

Але знову ж таки, другий .*жадібний, тому для другої групи він нічого не залишає.

  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                       |.*       )(.*|.*    )(.*|
  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                       |.*)(.*|.*)(.*|.*    )(.*|

Врешті-решт всі три групи відповідають, але четвертий примірник групи виходить з ладу. Почніть зворотній трек.

  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                |.*                         )(.*|
  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                |.*              )(.*|.*    )(.*|
  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                |.*       )(.*|.*)(.*|.*    )(.*|
  aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff|  gg  |  gg  |hhhhhh
^(.*                                |.*)(.*|.*)(.*|.*)(.*|.*    )(.*|

Ви можете бачити, як це спалює багато часу (діаграми навіть пропускають перегляд символів за символами, що відбувається насправді; вище показані лише "високі точки"). Проблема випливає з того, що попередній шматочок регексу жадібно збігається з тим, що пізніше частина регулярного виразів зрештою повинна буде відповідати, щоб отримати належну кількість повторень групи.

На мій вираз, кожне повторення ( [^|]*) ніколи не відповідає тому, що відповідає наступному елементу ( |), тому зворотний трек є чисто лінійним. Як тільки починається зворотний трекінг для кожного "стискаючого" матчу, він (за лінійним часом) виявить, що немає більш ранніх місць, де може відповідати наступний вираз; це змушує зворотний трек продовжувати попередній "скорочувальний" матч, поки нічого не збігається, і вся лінія не буде вирішена невідповідно.

Замість "нуля чи більше нетрубних, то труба" ( [^|]*|), також можна використовувати .з явно не жадібним повторенням ( \{-}у Vim, але воно змінюється; використовуються інші мови регулярного виведення *?).

^\(.\{-}|\)\{8}.*$

Це відмінне пояснення - дякую!
drapkin11

1

Що ж, у моєму комп’ютері це швидше:

:%s/\(|.\{-}\)\{8,}//n

Так, на моєму комп’ютері. Цей регекс виконує феноменально кращу роботу.
drapkin11
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.