2011-08-10 41 views
7

Este es un seguimiento y una complicación a esta pregunta: Extracting contents of a string within parentheses.Uso de expresiones regulares para extraer información de una cadena

En esa pregunta que tenía la siguiente cadena -

"Will Farrell (Nick Hasley), Rebecca Hall (Samantha)" 

y quería obtener una lista de tuplas en forma de (actor, character) -

[('Will Farrell', 'Nick Hasley'), ('Rebecca Hall', 'Samantha')] 

Para generalizar las cosas, tengo una cadena un poco más complicada, y necesito extraer la misma información. La cadena que tengo es -

"Will Ferrell (Nick Halsey), Rebecca Hall (Samantha), Glenn Howerton (Gary), 
with Stephen Root and Laura Dern (Delilah)" 

tengo que formatear de la siguiente manera:

[('Will Farrell', 'Nick Hasley'), ('Rebecca Hall', 'Samantha'), ('Glenn Howerton', 'Gary'), 
('Stephen Root',''), ('Lauren Dern', 'Delilah')] 

Sé que puedo reemplazar las palabras de relleno (con, y, &, etc.), pero puede '' No entiendo cómo agregar una entrada en blanco - '' - si no hay un nombre de personaje para el actor (en este caso Stephen Root). ¿Cuál sería la mejor manera de hacer esto?

Finalmente, necesito tener en cuenta si un actor tiene múltiples roles, y construir una tupla para cada función que tenga el actor. La cadena final que tengo es:

"Will Ferrell (Nick Halsey), Rebecca Hall (Samantha), Glenn Howerton (Gary, Brad), with 
Stephen Root and Laura Dern (Delilah, Stacy)" 

y necesito construir una lista de tuplas de la siguiente manera:

[('Will Farrell', 'Nick Hasley'), ('Rebecca Hall', 'Samantha'), ('Glenn Howerton', 'Gary'),  
('Glenn Howerton', 'Brad'), ('Stephen Root',''), ('Lauren Dern', 'Delilah'), ('Lauren Dern', 'Stacy')] 

Gracias.

+0

@ Michael: gracias por la ortografía de edición. – David542

+0

¿Es realmente necesario usar regex? – utdemir

+0

No, puede ser cualquier cosa. Lo que sea que funcione y sea lo mejor. – David542

Respuesta

4
import re 
credits = """Will Ferrell (Nick Halsey), Rebecca Hall (Samantha), Glenn Howerton (Gary, Brad), with 
Stephen Root and Laura Dern (Delilah, Stacy)""" 

# split on commas (only if outside of parentheses), "with" or "and" 
splitre = re.compile(r"\s*(?:,(?![^()]*\))|\bwith\b|\band\b)\s*") 

# match the part before the parentheses (1) and what's inside the parens (2) 
# (only if parentheses are present) 
matchre = re.compile(r"([^(]*)(?:\(([^)]*)\))?") 

# split the parts inside the parentheses on commas 
splitparts = re.compile(r"\s*,\s*") 

characters = splitre.split(credits) 
pairs = [] 
for character in characters: 
    if character: 
     match = matchre.match(character) 
     if match: 
      actor = match.group(1).strip() 
      if match.group(2): 
       parts = splitparts.split(match.group(2)) 
       for part in parts: 
        pairs.append((actor, part)) 
      else: 
       pairs.append((actor, "")) 

print(pairs) 

Salida:

[('Will Ferrell', 'Nick Halsey'), ('Rebecca Hall', 'Samantha'), 
('Glenn Howerton', 'Gary'), ('Glenn Howerton', 'Brad'), ('Stephen Root', ''), 
('Laura Dern', 'Delilah'), ('Laura Dern', 'Stacy')] 
0

Lo que queremos es identificar secuencias de palabras que comienzan con una letra mayúscula, además de algunas complicaciones (en mi humilde opinión no se puede asumir cada nombre está hecho de Nombre Apellido, sino también el nombre Apellido Jr., o Nombre M. Apellido, u otra variación localizada, Jean-Claude van Damme, Louis da Silva, etc.).

Ahora bien, esto es probable que sea exagerado para la entrada de muestra que publicó, pero como escribí anteriormente, supongo que las cosas pronto se volverán complicadas, así que abordaría esto usando nltk.

Aquí hay una muy crudo y no fragmento probado muy bien, pero debe hacer el trabajo:

import nltk 
from nltk.chunk.regexp import RegexpParser 

_patterns = [ 
    (r'^[A-Z][a-zA-Z]*[A-Z]?[a-zA-Z]+.?$', 'NNP'), # proper nouns 
    (r'^[(]$', 'O'), 
    (r'[,]', 'COMMA'), 
    (r'^[)]$', 'C'), 
    (r'.+', 'NN')         # nouns (default) 
] 

_grammar = """ 
     NAME: {<NNP> <COMMA> <NNP>} 
     NAME: {<NNP>+} 
     ROLE: {<O> <NAME>+ <C>} 
     """  
text = "Will Ferrell (Nick Halsey), Rebecca Hall (Samantha), Glenn Howerton (Gary, Brad), with Stephen Root and Laura Dern (Delilah, Stacy)" 
tagger = nltk.RegexpTagger(_patterns)  
chunker = RegexpParser(_grammar) 
text = text.replace('(', '(').replace(')', ')').replace(',', ' , ') 
tokens = text.split() 
tagged_text = tagger.tag(tokens) 
tree = chunker.parse(tagged_text) 

for n in tree: 
    if isinstance(n, nltk.tree.Tree) and n.node in ['ROLE', 'NAME']: 
     print n 

# output is: 
# (NAME Will/NNP Ferrell/NNP) 
# (ROLE (/O (NAME Nick/NNP Halsey/NNP))/C) 
# (NAME Rebecca/NNP Hall/NNP) 
# (ROLE (/O (NAME Samantha/NNP))/C) 
# (NAME Glenn/NNP Howerton/NNP) 
# (ROLE (/O (NAME Gary/NNP ,/COMMA Brad/NNP))/C) 
# (NAME Stephen/NNP Root/NNP) 
# (NAME Laura/NNP Dern/NNP) 
# (ROLE (/O (NAME Delilah/NNP ,/COMMA Stacy/NNP))/C) 

A continuación, debe procesar la salida etiquetado y poner nombres y funciones en una lista en lugar de impresión, pero obtener la imagen.

Lo que hacemos aquí es hacer un primer paso donde etiquetamos cada token de acuerdo con la expresión regular en _patterns, y luego hacemos una segunda pasada para construir fragmentos más complejos de acuerdo con su gramática simple. Puedes complicar la gramática y los patrones como quieras, es decir. captar variaciones de nombres, entradas desordenadas, abreviaturas, etc.

Creo que hacer esto con un solo pase de expresiones regulares va a ser un dolor para las entradas no triviales.

De lo contrario, Tim's solution resuelve el problema muy bien para la entrada que ha publicado, y sin la dependencia de nltk.

0

En caso de que quiera una solución no regex ... (No prevé paréntesis anidados.)

in_string = "Will Ferrell (Nick Halsey), Rebecca Hall (Samantha), Glenn Howerton (Gary, Brad), with Stephen Root and Laura Dern (Delilah, Stacy)"  

in_list = [] 
is_in_paren = False 
item = {} 
next_string = '' 

index = 0 
while index < len(in_string): 
    char = in_string[index] 

    if in_string[index:].startswith(' and') and not is_in_paren: 
     actor = next_string 
     if actor.startswith(' with '): 
      actor = actor[6:] 
     item['actor'] = actor 
     in_list.append(item) 
     item = {} 
     next_string = '' 
     index += 4  
    elif char == '(': 
     is_in_paren = True 
     item['actor'] = next_string 
     next_string = ''  
    elif char == ')': 
     is_in_paren = False 
     item['part'] = next_string 
     in_list.append(item) 
     item = {}     
     next_string = '' 
    elif char == ',': 
     if is_in_paren: 
      item['part'] = next_string 
      next_string = '' 
      in_list.append(item) 
      item = item.copy() 
      item.pop('part')     
    else: 
     next_string = "%s%s" % (next_string, char) 

    index += 1 


out_list = [] 
for dict in in_list: 
    actor = dict.get('actor') 
    part = dict.get('part') 

    if part is None: 
     part = '' 

    out_list.append((actor.strip(), part.strip())) 

print out_list 

Salida: [('Will Ferrell', 'Nick Halsey'), ('Rebecca Hall ',' Samantha '), (' Glenn Howerton ',' Gary '), (' Glenn Howerton ',' Brad '), (' Stephen Root ',' '), (' Laura Dern ',' Delilah '), (Laura Dern ', 'Stacy')]

1

solución de Tim Pietzcker se puede simplificar a (tenga en cuenta que los patrones se modifican también):

import re 
credits = """ Will Ferrell (Nick Halsey), Rebecca Hall (Samantha), Glenn Howerton (Gary, Brad), with 
Stephen Root and Laura Dern (Delilah, Stacy)""" 

# split on commas (only if outside of parentheses), "with" or "and" 
splitre = re.compile(r"(?:,(?![^()]*\))(?:\s*with)*|\bwith\b|\band\b)\s*") 

# match the part before the parentheses (1) and what's inside the parens (2) 
# (only if parentheses are present) 
matchre = re.compile(r"\s*([^(]*)(?<!)\s*(?:\(([^)]*)\))?") 

# split the parts inside the parentheses on commas 
splitparts = re.compile(r"\s*,\s*") 

pairs = [] 
for character in splitre.split(credits): 
    gr = matchre.match(character).groups('') 
    for part in splitparts.split(gr[1]): 
     pairs.append((gr[0], part)) 

print(pairs) 

continuación:

import re 
credits = """ Will Ferrell (Nick Halsey), Rebecca Hall (Samantha), Glenn Howerton (Gary, Brad), with 
Stephen Root and Laura Dern (Delilah, Stacy)""" 

# split on commas (only if outside of parentheses), "with" or "and" 
splitre = re.compile(r"(?:,(?![^()]*\))(?:\s*with)*|\bwith\b|\band\b)\s*") 

# match the part before the parentheses (1) and what's inside the parens (2) 
# (only if parentheses are present) 
matchre = re.compile(r"\s*([^(]*)(?<!)\s*(?:\(([^)]*)\))?") 

# split the parts inside the parentheses on commas 
splitparts = re.compile(r"\s*,\s*") 

gen = (matchre.match(character).groups('') for character in splitre.split(credits)) 

pp = [ (gr[0], part) for gr in gen for part in splitparts.split(gr[1])] 

print pp 

El truco es utilizar groups('') con un argumento ''

Cuestiones relacionadas