2010-02-17 32 views
54

Cuál es la forma Pythonic para dividir una cadena antes de las ocurrencias de un determinado conjunto de caracteres?dividir una cadena en mayúsculas

Por ejemplo, yo desea dividir 'TheLongAndWindingRoad' en cualquier ocurrencia de una letra mayúscula (posiblemente excepto el primero), y obtener ['The', 'Long', 'And', 'Winding', 'Road'].

Editar: También se debe dividir ocurrencias individuales, es decir de 'ABC' Me gustaría obtener ['A', 'B', 'C'].

Respuesta

78

Por desgracia, no es posible split on a zero-width match en Python. Pero se puede usar re.findall lugar:

>>> import re 
>>> re.findall('[A-Z][^A-Z]*', 'TheLongAndWindingRoad') 
['The', 'Long', 'And', 'Winding', 'Road'] 
>>> re.findall('[A-Z][^A-Z]*', 'ABC') 
['A', 'B', 'C'] 
+5

Tenga en cuenta que esto soltará cualquier carácter antes del primer carácter capital. 'theLongAndWindingRoad' daría como resultado ['Long', 'And', 'Winding', 'Road'] –

+6

@MarcSchulder: Si necesita ese caso, simplemente use ''[a-zA-Z] [^ AZ] *' 'como la expresión regular. – knub

4
import re 
filter(None, re.split("([A-Z][^A-Z]*)", "TheLongAndWindingRoad")) 

o

[s for s in re.split("([A-Z][^A-Z]*)", "TheLongAndWindingRoad") if s] 
+0

El filtro es totalmente innecesario y no le compra nada en una división directa de expresiones regulares con el grupo de captura: '[s para s en re.compile (r" ([AZ] [^ AZ] *) "). Split (" TheLongAndWindingRoad ") si s] 'dando' ['The', 'Long', 'And', 'Winding', 'Road'] ' – smci

+0

@smci: Este uso de' filter' es el mismo que el de la lista de comprensión con una condición. ¿Tienes algo en contra? – Gabe

+0

Sé que se puede reemplazar con una lista de comprensión con una condición, porque acabo de publicar ese código, y luego lo copió. Aquí hay tres razones de la lista por comprensión es preferible: a) * lenguaje legible: * listas por comprensión son un lenguaje más claro Pythonic y leer de izquierda a derecha de 'filtro (lambdaconditionfunc, ...)' b) en Python 3, 'filter()' devuelve un iterador. Entonces ellos no serán totalmente equivalentes. c) Espero 'filtro()' también es más lento – smci

17
>>> import re 
>>> re.findall('[A-Z][a-z]*', 'TheLongAndWindingRoad') 
['The', 'Long', 'And', 'Winding', 'Road'] 

>>> re.findall('[A-Z][a-z]*', 'SplitAString') 
['Split', 'A', 'String'] 

>>> re.findall('[A-Z][a-z]*', 'ABC') 
['A', 'B', 'C'] 

Si desea "It'sATest" para dividir a ["It's", 'A', 'Test'] cambio del rexeg a "[A-Z][a-z']*"

+0

+1: Primero para que ABC funcione. También actualicé mi respuesta ahora. –

+0

>>> re.findall ('[A-Z] [a-z] *', "Es aproximadamente el 70% de la economía") -----> ['It', 'Economy'] – ChristopheD

+0

@ChristopheD. El OP no dice cómo deben tratarse los caracteres que no son alfa. –

2

solución alternativa (si no me gustan las expresiones explícitas explícitas):

s = 'TheLongAndWindingRoad' 

pos = [i for i,e in enumerate(s) if e.isupper()] 

parts = [] 
for j in xrange(len(pos)): 
    try: 
     parts.append(s[pos[j]:pos[j+1]]) 
    except IndexError: 
     parts.append(s[pos[j]:]) 

print parts 
3

Una variación en @ChristopheD 's solución

s = 'TheLongAndWindingRoad' 

pos = [i for i,e in enumerate(s+'A') if e.isupper()] 
parts = [s[pos[j]:pos[j+1]] for j in xrange(len(pos)-1)] 

print parts 
+0

Bueno, esto también funciona con caracteres no latinos. Las soluciones de expresiones regulares que se muestran aquí no. – AlexVhr

20

Aquí es una solución regex alternativa. El problema se puede reprased como "¿Cómo se inserta un espacio antes de cada letra mayúscula, antes de hacer la división":

>>> s = "TheLongAndWindingRoad ABC A123B45" 
>>> re.sub(r"([A-Z])", r" \1", s).split() 
['The', 'Long', 'And', 'Winding', 'Road', 'A', 'B', 'C', 'A123', 'B45'] 

Esto tiene la ventaja de conservar todos los caracteres no está en blanco, lo que la mayoría de las otras soluciones no lo hacen.

2
src = 'TheLongAndWindingRoad' 
glue = ' ' 

result = ''.join(glue + x if x.isupper() else x for x in src).strip(glue).split(glue) 
+1

Podría agregar una explicación de por qué esta es una buena solución al problema. –

+0

Lo siento. Me olvidé el último paso – user3726655

0

Una forma alternativa sin necesidad de utilizar expresiones regulares o enumerar:

word = 'TheLongAndWindingRoad' 
list = [x for x in word] 

for char in list: 
    if char != list[0] and char.isupper(): 
     list[list.index(char)] = ' ' + char 

fin_list = ''.join(list).split(' ') 

Creo que es más clara y sencilla y sin encadenar demasiados métodos o el uso de una lista por comprensión largo que puede ser difícil de leer.

0

Una forma alternativa utilizando enumerate y isupper()

Código:

strs = 'TheLongAndWindingRoad' 
ind =0 
count =0 
new_lst=[] 
for index, val in enumerate(strs[1:],1): 
    if val.isupper(): 
     new_lst.append(strs[ind:index]) 
     ind=index 
if ind<len(strs): 
    new_lst.append(strs[ind:]) 
print new_lst 

Salida:

['The', 'Long', 'And', 'Winding', 'Road'] 
1

otro sin expresiones regulares y la capacidad de mantener mayúsculas contigua si se desea

def split_on_uppercase(s, keep_contiguous=False): 
    """ 

    Args: 
     s (str): string 
     keep_contiguous (bool): flag to indicate we want to 
           keep contiguous uppercase chars together 

    Returns: 

    """ 

    string_length = len(s) 
    is_lower_around = (lambda: s[i-1].islower() or 
         string_length > (i + 1) and s[i + 1].islower()) 

    start = 0 
    parts = [] 
    for i in range(1, string_length): 
     if s[i].isupper() and (not keep_contiguous or is_lower_around()): 
      parts.append(s[start: i]) 
      start = i 
    parts.append(s[start:]) 

    return parts 

>>> split_on_uppercase('theLongWindingRoad') 
['the', 'Long', 'Winding', 'Road'] 
>>> split_on_uppercase('TheLongWindingRoad') 
['The', 'Long', 'Winding', 'Road'] 
>>> split_on_uppercase('TheLongWINDINGRoadT', True) 
['The', 'Long', 'WINDING', 'Road', 'T'] 
>>> split_on_uppercase('ABC') 
['A', 'B', 'C'] 
>>> split_on_uppercase('ABCD', True) 
['ABCD'] 
>>> split_on_uppercase('') 
[''] 
>>> split_on_uppercase('hello world') 
['hello world'] 
0

Esto es posible con la herramienta more_itertools.split_before.

import more_itertools as mit 

iterable = "TheLongAndWindingRoad" 
[ "".join(i) for i in mit.split_before(iterable, lambda s: s.isupper())] 
# ['The', 'Long', 'And', 'Winding', 'Road'] 

También debe dividir ocurrencias individuales, es decir, desde 'ABC' que me gustaría obtener ['A', 'B', 'C'].

iterable = "ABC" 
[ "".join(i) for i in mit.split_before(iterable, lambda s: s.isupper())] 
# ['A', 'B', 'C'] 

more_itertools es un paquete de terceros con más de 60 herramientas útiles, incluyendo implementaciones para todos los originales itertools recipes, que evite su aplicación manual.