Temos dados de uma chamada de API REST armazenados em um arquivo de saída que se parece com o seguinte:
Arquivo de entrada de exemplo:
test test123 - test (bla bla1 (On chutti))
test test123 bla12 teeee (Rinku Singh)
balle balle (testagain) (Rohit Sharma)
test test123 test1111 test45345 (Surya) (Virat kohli (Lagaan))
testagain blae kaun hai ye banda (Ranbir kapoor (Lagaan), Milkha Singh (On chutti) (Lagaan))
Saída esperada:
bla bla1
Rinku Singh
Rohit Sharma
Virat kohli
Ranbir kapoor, Milkha Singh
Condições para derivar a saída esperada:
- Considere sempre a última ocorrência de parênteses () em cada linha. Precisamos extrair os valores dentro deste último par de parênteses mais externo.
- Dentro da última ocorrência de (), extraia todos os valores que aparecem antes de cada ocorrência de parênteses aninhados ().
- Por exemplo:
test test123 - test (bla bla1 (On chutti))
o último parêntesis começa de(bla
até até,chutti))
então preciso,bla bla1
pois é antes de inner(On chutti)
. Então, procure o último parêntesis e, em seguida, dentro de quantos pares de parêntesis vêm, precisamos obter dados antes deles, por exemplo: na linhatestagain blae kaun hai ye banda (Ranbir kapoor (Lagaan), Milkha Singh (On chutti) (Lagaan))
necessário éRanbir kapoor
eMilkha Singh
.
Tentativa de expressão regular: Tentei usar a seguinte expressão regular na demonstração funcional do regex :
Expressão regular:
^(?:^[^(]+\([^)]+\) \(([^(]+)\([^)]+\)\))|[^(]+\(([^(]+)\([^)]+\),\s([^\(]+)\([^)]+\)\s\([^\)]+\)\)|(?:(?:.*?)\((.*?)\(.*?\)\))|(?:[^(]+\(([^)]+)\))$
O Regex que testei está funcionando bem, mas quero melhorá-lo com o conselho de especialistas aqui.
Idiomas Preferidos: Procurando melhorar este regex OU um python, a resposta awk também está ok. Eu mesmo também tentarei adicionar uma awk
resposta.
Geralmente, expressões regulares não são apropriadas para analisar conjuntos aninhados de parênteses.
Aqui está um pequeno script Python que faz o que você pediu:
Eu usei
fileinput
para este PoC, mas deve ser trivial substituí-lo por qualquer que seja sua fonte de dados. Eu tentei com:e obtive o seguinte resultado:
Bônus:
Como forma de enfatizar a não utilização de regex para esse tipo de propósito, fiz um pouco de golfe de código e encurtei o script acima para o seguinte:
Ignorando a linha de importação (mas ainda contando todos os caracteres abaixo, incluindo recuos e retornos de linha), isso tem 136 caracteres de comprimento, 14 caracteres a menos que a expressão regular mostrada na pergunta. Esse código Python encurtado é (na minha opinião) ainda mais legível/manutenível/extensível do que qualquer regex que alguém possa inventar.
Suposições/entendimentos:
(
,)
) existem apenas como delimitadores (ou seja, não aparecem como parte dos dados)(
tem uma correspondência)
(
nos leva um nível abaixo)
nos leva um nível acimaThere is NO separator in output since in Python it was coming in capturing groups. In case of awk OR without capturing group's solution, can be separated with ,
]; para uma solução inicial, não removeremos nada; o OP sempre pode adicionar código para remover caracteres estranhosAmpliando o conjunto de dados atual do OP:
Demonstração de níveis:
Demonstração dos últimos dados do nível 2:
Uma
awk
ideia usando uma função recursiva para analisar dados delimitados por parênteses:Outra
awk
solução usando uma abordagem linear para análise sintática (semelhante à solução python de Pierre)Fazendo um teste (ambas as
awk
soluções acima geram a mesma saída, considerando a mesmalvl
configuração):Para
lvl=2
(solicitação do OP):Para
lvl=1
:Para
lvl=3
:Para
lvl=5
:Para
lvl=6
:Sempre que você estiver pensando em usar uma expressão regular longa e/ou complicada para tentar resolver um problema, tenha em mente a citação :
Usando qualquer awk:
Estou salvando uma cópia de
$0
inrec
e, então, no loop, estou convertendo cada(foo)
insiderec
para\nfoo\n
(assumindo o padrãoRS
e que oRS
não pode estar presente em umRS
registro separado por -) e também salvando ofoo
from$0
(para reter os pares originais(
e possivelmente aninhados)
) na variáveltgt
. Então, quando o loop termina,tgt
contém a últimafoo
substring que estava presente neste registro de entrada, por exemploRanbir kapoor (Lagaan), Milkha Singh (On chutti) (Lagaan)
, . Então, com o final,gsub()
removo todas(...)
as substrings detgt
, incluindo quaisquer espaços em branco ao redor, deixando apenas a saída desejada.Se você puder ter mais níveis de strings entre parênteses restantes
tgt
do que apenas 1 nível de profundidade, basta mudargsub(/ *\([^()]*) */, "", tgt)
parawhile ( gsub(/ *\([^()]*) */, "", tgt) );
.Com base apenas na entrada mostrada e nos seus comentários refletindo que você precisa capturar 1 ou 2 valores por linha, aqui está uma solução de regex otimizada:
Demonstração RegEx
Detalhes do RegEx:
Esta solução regex faz o seguinte:
Mais detalhes:
^
: Começar(?:
: Iniciar grupo de não captura\([^\n)(]*\)
: Corresponde a qualquer par de(...)
texto|
: OU[^()\n]
: Corresponde a qualquer caractere que não seja(
,)
e\n
)*
: Fim do grupo de não captura. Repita isso 0 ou mais vezes\(
: Última partida(
([^)(\n]+)
: 1º grupo de captura que corresponde ao texto com 1+ caracteres que não são(
,)
e\n
(?:
: Iniciar grupo de não captura 1\([^\n)(]*\)
: Corresponde a qualquer par de(...)
texto[, ]*
: Corresponde a 0 ou mais caracteres de espaço ou vírgula(?:
: Iniciar grupo de não captura 2([^)(\n]+)
: 2º grupo de captura que corresponde ao texto com 1+ caracteres que não são(
,)
e\n
)?
: Fim do grupo de não captura 2.?
torna esta uma correspondência opcional)?
: Fim do grupo de não captura 1.?
torna esta uma correspondência opcionalAqui está minha
awk
solução simples que funciona apenas com as substituições:Em seguida, execute-o como:
Se você estiver aberto a usar Python com o módulo regex PyPi, você pode usar nomes de grupos duplicados e então usar
captures("groupname")
para retornar uma lista de todas as capturas de um grupo.Esta regex pressupõe que há no máximo 1 nível de aninhamento onde pode haver 1 ou mais ocorrências do mesmo nome de grupo naquele nível.
A expressão regular corresponde a:
\(
Corresponder(
(?P<grp>[^()]+)
Nomeadogrp
para combinar com(...)
(?:
Grupo de não captura\([^()]*\)
(?:
Grupo de não captura,\s
Corresponder a uma vírgula e 1+ caracteres de espaço em branco(?P<grp>[^()]+)
Nomeadogrp
para combinar com(...)
(?:\s*\([^()]*\))*
Opcionalmente, combine 0+ caracteres de espaço em branco seguidos por(...)
)*
Feche o grupo de não captura)?
Feche o grupo e torne-o opcional\)
Corresponder)
$
Fim da sequênciaVeja uma demonstração de regex e uma demonstração de Python
Exemplo
Saída
Algumas pequenas adições
(?:.*?)
é o mesmo que.*?
Você pode omitir o grupo não capturado em algumas subpartes do seu regex, já que o grupo por si só não está sendo usado para uma alternância e não há quantificadores para esse grupo.*?\)\)
corresponde ao menor número possível de caracteres, seguida por))
onde o.*
próprio também pode corresponder a parênteses, onde pode corresponder involuntariamente a muitosVocê pode usar
nestedExpr
o módulo PyParsing :Impressões:
Como meu Input_file é sempre o mesmo padrão, é sempre o mesmo, sem casos extremos, vou escrevê-lo desta maneira. Escrito e testado em GNU
awk
. Usandomatch
funções com regex dentro delas e usando grupos de captura para armazenar valores em array nomeadosarr
que depois os imprimem conforme a necessidade.A saída será a seguinte.