Dividir cadenas en palabras con múltiples delimitadores de límite de palabra

Creo que lo que quiero hacer es una tarea bastante común, pero no he encontrado ninguna referencia en la web. Tengo texto con puntuación, y quiero una lista de las palabras.

"Hey, you - what are you doing here!?" 

debiera ser

 ['hey', 'you', 'what', 'are', 'you', 'doing', 'here'] 

Pero el str.split() Python solo funciona con un argumento, por lo que tengo todas las palabras con la puntuación después de dividirme con espacios en blanco. ¿Algunas ideas?

Un caso donde las expresiones regulares están justificadas:

 import re DATA = "Hey, you - what are you doing here!?" print re.findall(r"[\w']+", DATA) # Prints ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here'] 

re.split ()

re.split (patrón, cadena [, maxsplit = 0])

Dividir cadena por las ocurrencias de patrón. Si se usan paréntesis de captura en el patrón, entonces el texto de todos los grupos en el patrón también se devuelve como parte de la lista resultante. Si maxsplit es distinto de cero, como máximo se producen las divisiones maxsplit, y el rest de la cadena se devuelve como el elemento final de la lista. (Nota de incompatibilidad: en la versión original de Python 1.5, se ignoró maxsplit. Esto se ha corregido en versiones posteriores).

 >>> re.split('\W+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split('(\W+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split('\W+', 'Words, words, words.', 1) ['Words', 'words, words.'] 

Otra forma rápida de hacer esto sin una expresión regular es reemplazar los caracteres primero, como se muestra a continuación:

 >>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split() ['a', 'bcd', 'ef', 'g'] 

Tantas respuestas, sin embargo, no puedo encontrar ninguna solución que haga lo que literalmente pide el título de las preguntas (división en varios posibles separadores; en cambio, muchas respuestas eliminan cualquier cosa que no sea una palabra, que es diferente). Así que aquí hay una respuesta a la pregunta en el título, que se basa en el módulo estándar y eficiente de Python:

 >>> import re # Will be splitting on: ,  - ! ? : >>> filter(None, re.split("[, \-!?:]+", "Hey, you - what are you doing here!?")) ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here'] 

dónde:

  • el […] coincide con uno de los separadores que figuran en el interior,
  • la \- en la expresión regular está aquí para evitar la interpretación especial de - como un indicador de rango de caracteres (como en AZ ),
  • el + omite uno o más delimitadores (se podría omitir gracias al filter() , pero esto produciría innecesariamente cadenas vacías entre separadores emparejados), y
  • filter(None, …) elimina las cadenas vacías posiblemente creadas por separadores iniciales y finales (ya que las cadenas vacías tienen un valor booleano falso).

Este re.split() precisamente se “divide con varios separadores”, como se solicita en el título de la pregunta.

Esta solución es además inmune a los problemas con los caracteres que no son ASCII en las palabras que se encuentran en otras soluciones (consulte el primer comentario de la respuesta de ghostdog74 ).

El módulo re es mucho más eficiente (en velocidad y concisión) que hacer los bucles de Python y las pruebas “a mano”.

Otra forma, sin expresiones regulares.

 import string punc = string.punctuation thestring = "Hey, you - what are you doing here!?" s = list(thestring) ''.join([o for o in s if not o in punc]).split() 

Consejo profesional: use string.translate para las operaciones de cadena más rápidas que tiene Python.

Alguna prueba …

Primero, la manera lenta (perdón pprzemek):

 >>> import timeit >>> S = 'Hey, you - what are you doing here!?' >>> def my_split(s, seps): ... res = [s] ... for sep in seps: ... s, res = res, [] ... for seq in s: ... res += seq.split(sep) ... return res ... >>> timeit.Timer('my_split(S, punctuation)', 'from __main__ import S,my_split; from string import punctuation').timeit() 54.65477919578552 

A continuación, usamos re.findall() (como lo indica la respuesta sugerida). Mucho mas rápido:

 >>> timeit.Timer('findall(r"\w+", S)', 'from __main__ import S; from re import findall').timeit() 4.194725036621094 

Por último, utilizamos translate :

 >>> from string import translate,maketrans,punctuation >>> T = maketrans(punctuation, ' '*len(punctuation)) >>> timeit.Timer('translate(S, T).split()', 'from __main__ import S,T,translate').timeit() 1.2835021018981934 

Explicación:

string.translate se implementa en C y, a diferencia de muchas funciones de manipulación de cadenas en Python, string.translate no produce una nueva cadena. Así que es lo más rápido que puedes obtener para la sustitución de cadenas.

Sin embargo, es un poco incómodo, ya que necesita una tabla de traducción para hacer esta magia. Puede hacer una tabla de traducción con la función de conveniencia maketrans() . El objective aquí es traducir todos los caracteres no deseados a espacios. Un sustituto de uno por uno. De nuevo, no se producen nuevos datos. ¡Así que esto es rápido !

A continuación, utilizamos buena vieja split() . split() por defecto operará en todos los caracteres de espacio en blanco, agrupándolos para la división. El resultado será la lista de palabras que quieras. ¡Y este enfoque es casi 4 veces más rápido que re.findall() !

Respuesta tardía :), pero tuve un dilema similar y no quise usar el módulo ‘re’.

 def my_split(s, seps): res = [s] for sep in seps: s, res = res, [] for seq in s: res += seq.split(sep) return res print my_split('1111 2222 3333;4444,5555;6666', [' ', ';', ',']) ['1111', '', '2222', '3333', '4444', '5555', '6666'] 
 join = lambda x: sum(x,[]) # aka flatten1([[1],[2,3],[4]]) -> [1,2,3,4] # ...alternatively... join = lambda lists: [x for l in lists for x in l] 

Entonces esto se convierte en un triplete:

 fragments = [text] for token in tokens: fragments = join(f.split(token) for f in fragments) 

Explicación

Esto es lo que en Haskell se conoce como la mónada de la Lista. La idea detrás de la mónada es que una vez “en la mónada”, usted “permanezca en la mónada” hasta que algo lo saque. Por ejemplo, en Haskell, supongamos que asigna la función de range(n) -> [1,2,...,n] python sobre una Lista. Si el resultado es una Lista, se agregará a la Lista en el lugar, por lo que obtendría algo como un map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0] . Esto se conoce como map-append (o mappend, o tal vez algo así). La idea aquí es que tienes esta operación que estás aplicando (dividiendo en un token), y cada vez que lo haces, unes el resultado a la lista.

Puede abstraer esto en una función y tener tokens=string.punctuation de forma predeterminada.

Ventajas de este enfoque:

  • Este enfoque (a diferencia de los enfoques ingenuos basados ​​en expresiones regulares) puede funcionar con tokens de longitud arbitraria (que expresiones regulares también pueden hacer con una syntax más avanzada).
  • No estás restringido a meros tokens; podría tener una lógica arbitraria en lugar de cada token, por ejemplo, uno de los “tokens” podría ser una función que se divide de acuerdo a cómo están los paréntesis nesteds.

Primero, quiero estar de acuerdo con los demás en que las soluciones basadas en str.translate(...) regulares o str.translate(...) son más str.translate(...) . Para mi caso de uso, el desempeño de esta función no fue significativo, por lo que quise agregar ideas que consideré con ese criterio.

Mi objective principal era generalizar las ideas de algunas de las otras respuestas en una solución que podría funcionar para cadenas que contienen más que palabras de expresiones regulares (es decir, incluir en la lista negra el subconjunto explícito de caracteres de puntuación frente a caracteres de palabras en la lista blanca).

Tenga en cuenta que, en cualquier enfoque, también se podría considerar el uso de string.punctuation en lugar de una lista definida manualmente.

Opción 1 – re.sub

Me sorprendió ver que no hay respuesta hasta ahora utiliza re.sub (…) . Me parece un enfoque simple y natural para este problema.

 import re my_str = "Hey, you - what are you doing here!?" words = re.split(r'\s+', re.sub(r'[,\-!?]', ' ', my_str).strip()) 

En esta solución, re.sub(...) la llamada a re.sub(...) inside re.split(...) , pero si el rendimiento es crítico, comstackr la expresión regular fuera podría ser beneficioso, para mi caso de uso, la diferencia no fue Es significativo, así que prefiero la simplicidad y la legibilidad.

Opción 2 – str.replace

Estas son algunas líneas más, pero tiene la ventaja de ser expandible sin tener que verificar si necesita escapar de un determinado carácter en expresiones regulares.

 my_str = "Hey, you - what are you doing here!?" replacements = (',', '-', '!', '?') for r in replacements: my_str = my_str.replace(r, ' ') words = my_str.split() 

Hubiera sido bueno poder asignar el str.replace a la cadena, pero no creo que se pueda hacer con cadenas inmutables, y si bien la asignación de una lista de caracteres funcionaría, ejecutar cada reemplazo contra cada carácter suena excesivo (Editar: vea la siguiente opción para un ejemplo funcional).

Opción 3 – functools.reduce

(En Python 2, reduce está disponible en el espacio de nombres global sin importarlo desde functools).

 import functools my_str = "Hey, you - what are you doing here!?" replacements = (',', '-', '!', '?') my_str = functools.reduce(lambda s, sep: s.replace(sep, ' '), replacements, my_str) words = my_str.split() 

prueba esto:

 import re phrase = "Hey, you - what are you doing here!?" matches = re.findall('\w+', phrase) print matches 

esto imprimirá ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

Utilice reemplazar dos veces:

 a = '11223FROM33344INTO33222FROM3344' a.replace('FROM', ',,,').replace('INTO', ',,,').split(',,,') 

resultados en:

 ['11223', '33344', '33222', '3344'] 

Me gusta re , pero aquí está mi solución sin ella:

 from itertools import groupby sep = ' ,-!?' s = "Hey, you - what are you doing here!?" print [''.join(g) for k, g in groupby(s, sep.__contains__) if not k] 

sep .__ contiene__ es un método utilizado por el operador ‘in’. Básicamente es lo mismo que

 lambda ch: ch in sep 

pero es más conveniente aquí.

Groupby obtiene nuestra cadena y función. Divide la cadena en grupos usando esa función: siempre que un valor de la función cambia, se genera un nuevo grupo. Entonces, sep .__ contiene__ es exactamente lo que necesitamos.

groupby devuelve una secuencia de pares, donde pair [0] es el resultado de nuestra función y pair [1] es un grupo. Usando ‘si no k’ filtramos grupos con separadores (porque un resultado de sep .__ contiene__ es Verdadero en los separadores). Bueno, eso es todo. Ahora tenemos una secuencia de grupos donde cada uno es una palabra (el grupo es en realidad un iterable, así que usamos unir para convertirlo en una cadena).

Esta solución es bastante general, ya que utiliza una función para separar la cadena (puede dividirse por cualquier condición que necesite). Además, no crea cadenas / listas intermedias (puede eliminar la unión y la expresión se volverá perezosa, ya que cada grupo es un iterador)

En lugar de usar una función re módulo re.split, puede lograr el mismo resultado utilizando el método series.str.split de pandas.

Primero, cree una serie con la cadena anterior y luego aplique el método a la serie.

thestring = pd.Series("Hey, you - what are you doing here!?") thestring.str.split(pat = ',|-')

el parámetro pat toma los delimitadores y devuelve la cadena dividida como una matriz. Aquí los dos delimitadores se pasan usando un | (u operador). La salida es la siguiente:

[Hey, you , what are you doing here!?]

Me estoy reencontrando con Python y necesitaba lo mismo. La solución de Findall puede ser mejor, pero se me ocurrió esto:

 tokens = [x.strip() for x in data.split(',')] 

En Python 3, puedes usar el método de PY4E – Python para todos .

Podemos resolver ambos problemas utilizando los métodos de cadena lower , punctuation y translate . El translate es el más sutil de los métodos. Aquí está la documentación para translate :

your_string.translate(your_string.maketrans(fromstr, tostr, deletestr))

Reemplace los caracteres en fromstr con el carácter en la misma posición en tostr y borre todos los caracteres que están en deletestr . Las fromstr y tostr pueden ser cadenas vacías y se puede omitir el parámetro deletestr .

Puedes ver la “puntuación”:

 In [10]: import string In [11]: string.punctuation Out[11]: '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' 

Para su ejemplo:

 In [12]: your_str = "Hey, you - what are you doing here!?" In [13]: line = your_str.translate(your_str.maketrans('', '', string.punctuation)) In [14]: line = line.lower() In [15]: words = line.split() In [16]: print(words) ['hey', 'you', 'what', 'are', 'you', 'doing', 'here'] 

Para más información, puede consultar:

  • PY4E – Python para todos
  • traducir
  • str.maketrans
  • Python String Maketrans () Método

Usando Maketrans y Translate puede hacerlo de manera fácil y ordenada.

 import string specials = ',.!?:;"()<>[]#$=-/' trans = string.maketrans(specials, ' '*len(specials)) body = body.translate(trans) words = body.strip().split() 

Otra forma de lograrlo es utilizar el Kit de herramientas de lenguaje natural ( nltk ).

 import nltk data= "Hey, you - what are you doing here!?" word_tokens = nltk.tokenize.regexp_tokenize(data, r'\w+') print word_tokens 

Esto imprime: ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

El mayor inconveniente de este método es que necesita instalar el paquete nltk .

Los beneficios son que puedes hacer muchas cosas divertidas con el rest del paquete nltk una vez que obtengas tus tokens.

En primer lugar, no creo que su intención sea utilizar la puntuación como delimitadores en las funciones de división. Su descripción sugiere que simplemente desea eliminar la puntuación de las cadenas resultantes.

Me encuentro con esto con bastante frecuencia, y mi solución habitual no requiere re.

Función lambda de una línea con comprensión de lista:

(requiere import string ):

 split_without_punc = lambda text : [word.strip(string.punctuation) for word in text.split() if word.strip(string.punctuation) != ''] # Call function split_without_punc("Hey, you -- what are you doing?!") # returns ['Hey', 'you', 'what', 'are', 'you', 'doing'] 

Función (tradicional)

Como una función tradicional, esto sigue siendo solo dos líneas con una lista de comprensión (además de la import string ):

 def split_without_punctuation2(text): # Split by whitespace words = text.split() # Strip punctuation from each word return [word.strip(ignore) for word in words if word.strip(ignore) != ''] split_without_punctuation2("Hey, you -- what are you doing?!") # returns ['Hey', 'you', 'what', 'are', 'you', 'doing'] 

También naturalmente dejará intactas las contracciones y las palabras con guiones. Siempre puede usar text.replace("-", " ") para convertir los guiones en espacios antes de la división.

Función general sin Lambda o comprensión de lista

Para una solución más general (donde puede especificar los caracteres a eliminar), y sin una lista de comprensión, obtendrá:

 def split_without(text: str, ignore: str) -> list: # Split by whitespace split_string = text.split() # Strip any characters in the ignore string, and ignore empty strings words = [] for word in split_string: word = word.strip(ignore) if word != '': words.append(word) return words # Situation-specific call to general function import string final_text = split_without("Hey, you - what are you doing?!", string.punctuation) # returns ['Hey', 'you', 'what', 'are', 'you', 'doing'] 

Por supuesto, también puede generalizar la función lambda a cualquier cadena de caracteres especificada.

En primer lugar, siempre use re.compile () antes de realizar cualquier operación RegEx en un bucle porque funciona más rápido que la operación normal.

así que para su problema primero compile el patrón y luego realice la acción en él.

 import re DATA = "Hey, you - what are you doing here!?" reg_tok = re.compile("[\w']+") print reg_tok.findall(DATA) 

Aquí está la respuesta con alguna explicación.

 st = "Hey, you - what are you doing here!?" # replace all the non alpha-numeric with space and then join. new_string = ''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st]) # output of new_string 'Hey you what are you doing here ' # str.split() will remove all the empty string if separator is not provided new_list = new_string.split() # output of new_list ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here'] # we can join it to get a complete string without any non alpha-numeric character ' '.join(new_list) # output 'Hey you what are you doing' 

o en una línea, podemos hacer esto:

 (''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])).split() # output ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here'] 

respuesta actualizada

Cree una función que tome como entrada dos cadenas (la cadena de origen que se dividirá y la cadena de lista de delimitadores) y generará una lista de palabras divididas:

 def split_string(source, splitlist): output = [] # output list of cleaned words atsplit = True for char in source: if char in splitlist: atsplit = True else: if atsplit: output.append(char) # append new word after split atsplit = False else: output[-1] = output[-1] + char # continue copying characters until next split return output 

tengo el mismo problema que @ooboo y encontrar este tema @ ghostdog74 me inspiró, tal vez alguien encuentre útil mi solución

 str1='adj:sg:nom:m1.m2.m3:pos' splitat=':.' ''.join([ s if s not in splitat else ' ' for s in str1]).split() 

ingrese algo en el espacio y divídalo usando el mismo carácter si no quiere dividir los espacios.

Aquí está mi ir a una división con múltiples eliminadores:

 def msplit( str, delims ): w = '' for z in str: if z not in delims: w += z else: if len(w) > 0 : yield w w = '' if len(w) > 0 : yield w 

Creo que la siguiente es la mejor respuesta para satisfacer sus necesidades:

\W+ puede ser adecuado para este caso, pero puede no serlo para otros casos.

 filter(None, re.compile('[ |,|\-|!|?]').split( "Hey, you - what are you doing here!?") 

Heres mi toma en ello …

 def split_string(source,splitlist): splits = frozenset(splitlist) l = [] s1 = "" for c in source: if c in splits: if s1: l.append(s1) s1 = "" else: print s1 s1 = s1 + c if s1: l.append(s1) return l >>>out = split_string("First Name,Last Name,Street Address,City,State,Zip Code",",") >>>print out >>>['First Name', 'Last Name', 'Street Address', 'City', 'State', 'Zip Code'] 

Me gusta el replace() la mejor manera. The following procedure changes all separators defined in a string splitlist to the first separator in splitlist and then splits the text on that one separator. It also accounts for if splitlist happens to be an empty string. It returns a list of words, with no empty strings in it.

 def split_string(text, splitlist): for sep in splitlist: text = text.replace(sep, splitlist[0]) return filter(None, text.split(splitlist[0])) if splitlist else [text] 
 def get_words(s): l = [] w = '' for c in s.lower(): if c in '-!?,. ': if w != '': l.append(w) w = '' else: w = w + c if w != '': l.append(w) return l 

Here is the usage:

 >>> s = "Hey, you - what are you doing here!?" >>> print get_words(s) ['hey', 'you', 'what', 'are', 'you', 'doing', 'here'] 

If you want a reversible operation (preserve the delimiters), you can use this function:

 def tokenizeSentence_Reversible(sentence): setOfDelimiters = ['.', ' ', ',', '*', ';', '!'] listOfTokens = [sentence] for delimiter in setOfDelimiters: newListOfTokens = [] for ind, token in enumerate(listOfTokens): ll = [([delimiter, w] if ind > 0 else [w]) for ind, w in enumerate(token.split(delimiter))] listOfTokens = [item for sublist in ll for item in sublist] # flattens. listOfTokens = filter(None, listOfTokens) # Removes empty tokens: '' newListOfTokens.extend(listOfTokens) listOfTokens = newListOfTokens return listOfTokens 

You want Python’s RegEx module’s findall() method:

http://www.regular-expressions.info/python.html

Example