Seu regex é propenso a se deparar com algum comportamento O (N ^ 2) do mecanismo regex "backtracking" usado no Vim (e em muitos outros idiomas e ambientes).
Felizmente, existem maneiras de escrever expressões equivalentes que não causam retrocesso excessivo. Por exemplo:
/^\([^|]*|\)\{8}.*$
Em geral, você não precisa corresponder a “oito ou mais”, pois se você já sabe que a linha é problemática se tiver oito (se tem mais ou não).
Se você realmente precisar corresponder a linha inteira (por exemplo, porque é parte de uma operação :s
), será necessário manter a última parte ( .*$
); Se você está apenas usando o regex para encontrar as "oito ou mais" linhas, então você pode deixar .*$
do final.
Além disso, aconselho apenas tentar combinar um "lado" do tubo dentro do grupo que você repita. Isso simplifica tanto o pensamento sobre como a regex combina linhas, quanto o próprio mecanismo de regex é executado (elimina uma fonte de retrocesso).
Agora, para explicar o pouco sobre "retrocesso". Considere que você tem uma linha com oito caracteres de pipe:
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
A passagem a seguir descreve como o mecanismo regex tenta combinar sua expressão com a linha acima (adicionei espaço em branco extra às linhas regex para mostrar (aproximadamente) onde partes da regex correspondem aos caracteres da própria linha).
O primeiro .*
é ganancioso e combinará tudo com o final da linha, deixando o caractere de canal inigualável.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
^(.* |
A mais recente correspondência "encolhível" desiste de partes do seu jogo e tenta o resto do regex novamente. Isso acontece um caractere por vez neste caso (já que .
corresponderá a qualquer caractere único). Esse backtracking continua até que o restante da expressão possa corresponder (ou até voltar ao início - que é a única maneira pela qual a linha não corresponde à expressão!).
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
^(.* |.* )(.*|
Assim, o primeiro .*
recuou o suficiente para deixar o restante do grupo igualar, mas não havia nada para o segundo grupo igualar. Hora de recuar um pouco mais.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
^(.* |.* )(.*|
O backtracking encontrou um novo ponto de "parada", mas agora o segundo .*
no primeiro grupo está fazendo sua correspondência gulosa. O segundo grupo não corresponde. Backtracking do segundo .*
no primeiro grupo começa.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
^(.* |.*)(.*|.* )(.*|
O segundo grupo encontrou uma correspondência, mas o terceiro não correspondeu. Retroceda novamente, começando com a correspondência mais recente. O segundo .*
do segundo grupo volta atrás para nada. O primeiro .*
do segundo grupo retrocede para nada. O segundo .*
do primeiro grupo retrocede para nada. O primeiro .*
do primeiro grupo recua com sucesso.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
^(.* |.* )(.*|
Mas, novamente, o segundo .*
é ganancioso, por isso não deixa nada para o segundo grupo.
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
^(.* |.* )(.*|.* )(.*|
aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh
^(.* |.*)(.*|.*)(.*|.* )(.*|
Eventualmente, todos os três grupos correspondem, mas a quarta instância do grupo falha. Comece a recuar.
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
^(.* |.*)(.*|.*)(.*|.*)(.*|.* )(.*|
Você pode ver como isso queima muito tempo (os diagramas até pulam o backtracking de caractere por caractere que realmente ocorre; somente os “pontos altos” são mostrados acima). O problema vem de ter um bit anterior do regex correspondentemente com algo que a parte posterior do regex eventualmente terá que combinar para obter o número apropriado de repetições do grupo.
Na minha expressão, cada repetição ( [^|]*
) nunca corresponde a nada que o elemento a seguir ( |
) corresponderia, portanto, o retrocesso é puramente linear. Depois que o retrocesso iniciar para cada correspondência "encolhível", ele descobrirá (em tempo linear) que não há lugares anteriores nos quais a expressão a seguir possa corresponder; isso força o retrocesso a continuar com a correspondência anterior "encolhível" até que nada seja igualado e a linha inteira seja decidida como não correspondendo.
Em vez de "zero ou mais não pipe, em seguida, pipe" ( [^|]*|
), também é possível usar .
com uma repetição explicitamente não voraz ( \{-}
no Vim, mas isso varia; outros linguagens regex usam *?
).
^\(.\{-}|\)\{8}.*$