¿Dividir eficientemente una cadena usando varios separadores y reteniendo cada separador?

Necesito dividir cadenas de datos usando cada carácter de string.punctuation y string.whitespace como separador.

Además, necesito que los separadores permanezcan en la lista de salida, entre los elementos que separaron en la cadena.

Por ejemplo,

 "Now is the winter of our discontent" 

debe salir:

 ['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent'] 

No estoy seguro de cómo hacer esto sin tener que recurrir a una orgía de bucles nesteds, lo que es inaceptablemente lento. ¿Cómo puedo hacerlo?

Un enfoque diferente a los no regex de los otros:

 >>> import string >>> from itertools import groupby >>> >>> special = set(string.punctuation + string.whitespace) >>> s = "One two three tab\ttabandspace\t end" >>> >>> split_combined = [''.join(g) for k, g in groupby(s, lambda c: c in special)] >>> split_combined ['One', ' ', 'two', ' ', 'three', ' ', 'tab', '\t', 'tabandspace', '\t ', 'end'] >>> split_separated = [''.join(g) for k, g in groupby(s, lambda c: c if c in special else False)] >>> split_separated ['One', ' ', 'two', ' ', 'three', ' ', 'tab', '\t', 'tabandspace', '\t', ' ', 'end'] 

Podría usar dict.fromkeys y .get lugar de la lambda , supongo.

[editar]

Alguna explicación:

groupby acepta dos argumentos, una función iterable y una tecla (opcional). Recorre lo iterable y los agrupa con el valor de la función clave:

 >>> groupby("sentence", lambda c: c in 'nt')  >>> [(k, list(g)) for k,g in groupby("sentence", lambda c: c in 'nt')] [(False, ['s', 'e']), (True, ['n', 't']), (False, ['e']), (True, ['n']), (False, ['c', 'e'])] 

donde se agrupan los términos con valores contiguos de la función clave. (En realidad, esta es una fuente común de errores: la gente olvida que primero tienen que ordenar por función del teclado si desean agrupar términos que podrían no ser secuenciales).

Como adivinó @JonClements, lo que tenía en mente era

 >>> special = dict.fromkeys(string.punctuation + string.whitespace, True) >>> s = "One two three tab\ttabandspace\t end" >>> [''.join(g) for k,g in groupby(s, special.get)] ['One', ' ', 'two', ' ', 'three', ' ', 'tab', '\t', 'tabandspace', '\t ', 'end'] 

Para el caso donde estábamos combinando los separadores. .get devuelve None si el valor no está en el dict.

 import re import string p = re.compile("[^{0}]+|[{0}]+".format(re.escape( string.punctuation + string.whitespace))) print p.findall("Now is the winter of our discontent") 

No soy un gran fan de usar expresiones regulares para todos los problemas, pero no creo que tenga muchas opciones en esto si lo desea rápido y corto.

Te explicaré la expresión regular ya que no estás familiarizado con ella:

  • [...] significa cualquiera de los caracteres dentro de los corchetes
  • [^...] significa cualquiera de los caracteres que no están dentro de los corchetes
  • + detrás significa uno o más de lo anterior
  • x|y significa hacer coincidir x o y

Por lo tanto, la expresión regular coincide con 1 o más caracteres donde todos deben ser puntuación y espacios en blanco, o ninguno debe serlo. El método findall encuentra todas las coincidencias no superpuestas del patrón.

Prueba esto:

 import re re.split('(['+re.escape(string.punctuation + string.whitespace)+']+)',"Now is the winter of our discontent") 

Explicación de la documentación de Python :

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.

Solución en tiempo lineal ( O(n) ):

Digamos que tienes una cuerda:

 original = "a, b...cd" 

Primero convierta todos los separadores al espacio:

 splitters = string.punctuation + string.whitespace trans = string.maketrans(splitters, ' ' * len(splitters)) s = original.translate(trans) 

Ahora s == 'abc d' . Ahora puede usar itertools.groupby para alternar entre espacios y no espacios:

 result = [] position = 0 for _, letters in itertools.groupby(s, lambda c: c == ' '): letter_count = len(list(letters)) result.append(original[position:position + letter_count]) position += letter_count 

Ahora result == ['a', ', ', 'b', '...', 'c', ' ', 'd'] , que es lo que necesitas.

Mi toma:

 from string import whitespace, punctuation import re pattern = re.escape(whitespace + punctuation) print re.split('([' + pattern + '])', 'now is the winter of') 

Dependiendo del texto con el que esté trabajando, puede simplificar su concepto de delimitadores a “cualquier otra cosa que no sean letras y números”. Si esto funciona, puede utilizar la siguiente solución de expresiones regulares:

 re.findall(r'[a-zA-Z\d]+|[^a-zA-Z\d]', text) 

Esto supone que desea dividirse en cada carácter delimitador individual incluso si aparecen consecutivamente, por lo que 'foo..bar' se convertiría en ['foo', '.', '.', 'bar'] . Si en cambio esperas ['foo', '..', 'bar'] , usa [a-zA-Z\d]+|[^a-zA-Z\d]+ (solo la diferencia es agregar + en la Muy al final).

 from string import punctuation, whitespace s = "..test. and stuff" f = lambda s, c: s + ' ' + c + ' ' if c in punctuation else s + c l = sum([reduce(f, word).split() for word in s.split()], []) print l 

Para cualquier colección arbitraria de separadores:

 def separate(myStr, seps): answer = [] temp = [] for char in myStr: if char in seps: answer.append(''.join(temp)) answer.append(char) temp = [] else: temp.append(char) answer.append(''.join(temp)) return answer In [4]: print separate("Now is the winter of our discontent", set(' ')) ['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent'] In [5]: print separate("Now, really - it is the winter of our discontent", set(' ,-')) ['Now', ',', '', ' ', 'really', ' ', '', '-', '', ' ', 'it', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent'] 

Espero que esto ayude

 from itertools import chain, cycle, izip s = "Now is the winter of our discontent" words = s.split() wordsWithWhitespace = list( chain.from_iterable( izip( words, cycle([" "]) ) ) ) # result : ['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent', ' ']