¿Cómo dividir una cadena por comas colocadas fuera de paréntesis?

Tengo una cadena de tal formato:

"Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)" 

así que, básicamente, es una lista de los nombres de los actores (opcionalmente, seguido de su rol entre paréntesis). El rol en sí puede contener una coma (el nombre del actor no puede, espero que así sea).

Mi objective es dividir esta cadena en una lista de pares – (actor name, actor role) .

Una solución obvia sería revisar cada carácter, verificar las apariciones de '(' , ')' y ',' y dividirlo cada vez que ocurra una coma fuera. Pero esto parece un poco pesado …

Estaba pensando en dividirlo usando una expresión regular: primero divida la cadena por paréntesis:

 import re x = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)" s = re.split(r'[()]', x) # ['Wilbur Smith ', 'Billy, son of John', ', Eddie Murphy ', 'John', ', Elvis Presley, Jane Doe ', 'Jane Doe', ''] 

Los elementos impares aquí son los nombres de los actores, incluso son los roles. Entonces podría dividir los nombres por comas y extraer de alguna manera los pares nombre-rol. Pero esto parece aún peor que mi primer enfoque.

¿Hay alguna forma más fácil / más agradable de hacer esto, ya sea con una sola expresión regular o con una buena pieza de código?

Una forma de hacerlo es usar findall con una expresión regular que coincida con las cosas que pueden ir entre separadores. p.ej:

 >>> s = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)" >>> r = re.compile(r'(?:[^,(]|\([^)]*\))+') >>> r.findall(s) ['Wilbur Smith (Billy, son of John)', ' Eddie Murphy (John)', ' Elvis Presley', ' Jane Doe (Jane Doe)'] 

La expresión regular anterior coincide con uno o más:

  • caracteres sin coma, sin paréntesis
  • cadenas que comienzan con un parén abierto, contienen 0 o más paréntesis no cerradas y luego un parén cerrado

Una peculiaridad de este enfoque es que los separadores adyacentes se tratan como un solo separador. Es decir, no verá una cadena vacía. Eso puede ser un error o una característica dependiendo de su caso de uso.

También tenga en cuenta que las expresiones regulares no son adecuadas para los casos en que el anidamiento es una posibilidad. Entonces, por ejemplo, esto se dividiría incorrectamente:

 "Wilbur Smith (son of John (Johnny, son of James), aka Billy), Eddie Murphy (John)" 

Si tiene que lidiar con anidar, su mejor opción sería dividir la cadena en parens, comas y todo lo demás (esencialmente tokenizing (esta parte aún podría hacerse con expresiones regulares) y luego recorrer esos tokens que vuelven a ensamblar los campos, manteniendo realice un seguimiento de su nivel de anidamiento a medida que avanza (este seguimiento del nivel de anidamiento es lo que las expresiones regulares no pueden hacer por sí mismas).

Creo que la mejor manera de abordar esto sería usar el módulo csv incorporado de python.

Debido a que el módulo csv solo permite una quotechar un carácter, necesitaría hacer un reemplazo en sus entradas para convertir () a algo como | o " . Luego, asegúrese de estar usando un dialecto apropiado y listo.

 s = re.split(r',\s*(?=[^)]*(?:\(|$))', x) 

El lookahead hace coincidir todo hasta el siguiente paréntesis abierto o el final de la cadena, si no hay un paréntesis intermedio entre ellos. Eso asegura que la coma no esté dentro de un conjunto de paréntesis.

Un bash de expresión regular legible por humanos:

 import re regex = re.compile(r""" # name starts and ends on word boundary # no '(' or commas in the name (?P\b[^(,]+\b) \s* # everything inside parentheses is a role (?:\( (?P[^)]+) \))? # role is optional """, re.VERBOSE) s = ("Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley," "Jane Doe (Jane Doe)") print re.findall(regex, s) 

Salida:

 [('Wilbur Smith', 'Billy, son of John'), ('Eddie Murphy', 'John'), ('Elvis Presley', ''), ('Jane Doe', 'Jane Doe')] 

Mi respuesta no utilizará regex.

Creo que el escáner de caracteres simple con el estado ” in_actor_name ” debería funcionar. Recuerde que el estado ” in_actor_name ” se termina con ‘)’ o con una coma en este estado.

Mi bash:

 s = 'Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)' in_actor_name = 1 role = '' name = '' for c in s: if c == ')' or (c == ',' and in_actor_name): in_actor_name = 1 name = name.strip() if name: print "%s: %s" % (name, role) name = '' role = '' elif c == '(': in_actor_name = 0 else: if in_actor_name: name += c else: role += c if name: print "%s: %s" % (name, role) 

Salida:

 Wilbur Smith: Billy, son of John Eddie Murphy: John Elvis Presley: Jane Doe: Jane Doe 

Aquí hay una técnica general que he usado en el pasado para estos casos:

Utilice la sub del módulo re con una función como argumento de reemplazo. La función realiza un seguimiento de la apertura y el cierre de parens, corchetes y llaves, así como comillas simples y dobles, y realiza un reemplazo solo fuera de dichas subcadenas entre corchetes y entre comillas. Luego, puede reemplazar las comas entre corchetes / comillas con otro carácter que está seguro de que no aparece en la cadena (uso el código ASCII / Unicode group-separator: chr (29)), luego haga una cadena simple. dividido en ese personaje. Aquí está el código:

 import re def srchrepl(srch, repl, string): """Replace non-bracketed/quoted occurrences of srch with repl in string""" resrchrepl = re.compile(r"""(?P[([{])|(?P['"])|(?P[""" + srch + """])|(?P[)\]}])""") return resrchrepl.sub(_subfact(repl), string) def _subfact(repl): """Replacement function factory for regex sub method in srchrepl.""" level = 0 qtflags = 0 def subf(mo): nonlocal level, qtflags sepfound = mo.group('sep') if sepfound: if level == 0 and qtflags == 0: return repl else: return mo.group(0) elif mo.group('lbrkt'): level += 1 return mo.group(0) elif mo.group('quote') == "'": qtflags ^= 1 # toggle bit 1 return "'" elif mo.group('quote') == '"': qtflags ^= 2 # toggle bit 2 return '"' elif mo.group('rbrkt'): level -= 1 return mo.group(0) return subf 

Si no tiene una versión no nonlocal en su versión de Python, simplemente cámbiela a global y defina level y qtflags en el nivel de módulo.

Así es como se usa:

 >>> GRPSEP = chr(29) >>> string = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)" >>> lst = srchrepl(',', GRPSEP, string).split(GRPSEP) >>> lst ['Wilbur Smith (Billy, son of John)', ' Eddie Murphy (John)', ' Elvis Presley', ' Jane Doe (Jane Doe)'] 

Este post me ayudó mucho. Estaba buscando dividir una cadena por comas colocadas fuera de las comillas. Utilicé esto como un arranque. Mi última línea de código fue regEx = re.compile(r'(?:[^,"]|"[^"]*")+') Esto funcionó. Gracias una tonelada.

Ciertamente estoy de acuerdo con @Wogan arriba, que usar el moudle CSV es un buen enfoque. Dicho esto, si aún desea probar una solución de expresiones regulares, inténtelo, pero tendrá que adaptarlo al dialecto de Python.

 string.split(/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/) 

HTH

dividido por “)”

 >>> s="Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)" >>> s.split(")") ['Wilbur Smith (Billy, son of John', ', Eddie Murphy (John', ', Elvis Presley, Jane Doe (Jane Doe', ''] >>> for i in s.split(")"): ... print i.split("(") ... ['Wilbur Smith ', 'Billy, son of John'] [', Eddie Murphy ', 'John'] [', Elvis Presley, Jane Doe ', 'Jane Doe'] [''] 

puede hacer una verificación adicional para obtener los nombres que no vienen con ().

Ninguna de las respuestas anteriores es correcta si hay algún error o ruido en sus datos.

Es fácil encontrar una buena solución si sabe que los datos son correctos en todo momento. Pero, ¿qué pasa si hay errores de formato? ¿Qué quieres que pase?

Supongamos que hay paréntesis de anidación? Supongamos que hay paréntesis inigualables? Supongamos que la cadena termina con o comienza con una coma, o tiene dos en una fila?

Todas las soluciones anteriores producirán más o menos basura y no se lo comunicaremos a usted.

Si dependiera de mí, comenzaría con una restricción bastante estricta sobre lo que eran los datos “correctos”, sin paréntesis de anidamiento, sin paréntesis sin plots, y sin segmentos vacíos antes, entre o después de los comentarios, validar a medida que avanzaba y luego subir una excepción si no pude validar.