Divida una cadena en letras mayúsculas

94

¿Cuál es la forma pitónica de dividir una cadena antes de que ocurra un conjunto de caracteres determinado?

Por ejemplo, quiero dividir 'TheLongAndWindingRoad' en cualquier aparición de una letra mayúscula (posiblemente excepto la primera) y obtener ['The', 'Long', 'And', 'Winding', 'Road'].

Editar: También debería dividir ocurrencias individuales, es decir, de las 'ABC'que me gustaría obtener ['A', 'B', 'C'].

Federico A. Ramponi
fuente

Respuestas:

137

Desafortunadamente, no es posible dividir en una coincidencia de ancho cero en Python. Pero puedes usar re.findallen su 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']
Mark Byers
fuente
13
Tenga en cuenta que esto colocará cualquier carácter antes del primer carácter en mayúscula. 'theLongAndWindingRoad' resultaría en ['Long', 'And', 'Winding', 'Road']
Marc Schulder
15
@MarcSchulder: Si necesita ese caso, utilícelo '[a-zA-Z][^A-Z]*'como expresión regular.
knub
¿Es posible hacer lo mismo sin upercase?
Laurent Cesaro
3
Para dividir las palabras en minúsculas de camelloprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant
32

Aquí hay una solución alternativa de expresiones regulares. El problema se puede expresar como "¿cómo inserto un espacio antes de cada letra mayúscula, antes de dividir":

>>> 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 preservar todos los caracteres que no son espacios en blanco, lo que la mayoría de las otras soluciones no hacen.

Dave Kirby
fuente
¿Puede explicar por qué funciona el espacio antes de \ 1? ¿Es por el método de división o está relacionado con la expresión regular?
Lax_Sam
el delimitador dividido tiene como valor predeterminado cualquier cadena de espacios en blanco
CIsForCookies
20
>>> 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"dividir para ["It's", 'A', 'Test']cambiar el rexeg a"[A-Z][a-z']*"

John La Rooy
fuente
+1: Primero para que ABC funcione. También actualicé mi respuesta ahora.
Mark Byers
>>> re.findall ('[AZ] [az] *', "Es aproximadamente el 70% de la economía") -----> ['It', 'Economy']
ChristopheD
@ChristopheD. El OP no dice cómo se deben tratar los caracteres no alfabéticos.
John La Rooy
1
cierto, pero esta forma actual de expresiones dropsregulares también todas las palabras regulares (simplemente alfa) que no comienzan con una letra mayúscula. Dudo que esa fuera la intención del OP.
ChristopheD
8

Una variación de la solución de @ChristopheD

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
pwdyson
fuente
2
Agradable, esto también funciona con caracteres no latinos. Las soluciones de expresiones regulares que se muestran aquí no lo hacen.
AlexVhr
7

Utilice una anticipación:

En Python 3.7, puede hacer esto:

re.split('(?=[A-Z])', 'theLongAndWindingRoad')

Y rinde:

['the', 'Long', 'And', 'Winding', 'Road']
Endlisnis
fuente
5
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]
Gabe
fuente
1
El filtro es totalmente innecesario y no le compra nada a través de una división directa de expresiones regulares con grupo de captura: [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]dando['The', 'Long', 'And', 'Winding', 'Road']
smci
1
@smci: este uso de filteres el mismo que la comprensión de la lista con una condición. ¿Tienes algo en contra?
Gabe
1
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 copiaste. Aquí hay tres razones por las que es preferible la comprensión de la lista: a) Modismo legible: las comprensiones de lista son un lenguaje más Pythonic y se leen más claro de izquierda a derecha que filter(lambdaconditionfunc, ...)b) en Python 3, filter()devuelve un iterador. Por tanto, no serán totalmente equivalentes. c) Supongo que también filter()es más lento
smci
4
src = 'TheLongAndWindingRoad'
glue = ' '

result = ''.join(glue + x if x.isupper() else x for x in src).strip(glue).split(glue)
usuario3726655
fuente
1
¿Podría agregar una explicación de por qué esta es una buena solución al problema?
Matas Vaitkevicius
Lo siento. Olvidé el último paso
user3726655
Me parece conciso, pitónico y autoexplicativo.
4

Creo que una mejor respuesta podría ser dividir la cadena en palabras que no terminen en mayúscula. Esto manejaría el caso en el que la cadena no comience con una letra mayúscula.

 re.findall('.[^A-Z]*', 'aboutTheLongAndWindingRoad')

ejemplo:

>>> import re
>>> re.findall('.[^A-Z]*', 'aboutTheLongAndWindingRoadABC')
['about', 'The', 'Long', 'And', 'Winding', 'Road', 'A', 'B', 'C']
musaraña
fuente
2

Solución alternativa (si no le gustan las expresiones regulares 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
Cristóbal
fuente
1

Otro sin expresiones regulares y la capacidad de mantener mayúsculas contiguas 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']
Totoro
fuente
1

Esto es posible con la more_itertools.split_beforeherramienta.

import more_itertools as mit


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

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

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

more_itertoolses un paquete de terceros con más de 60 herramientas útiles que incluyen implementaciones para todas las recetas originales de itertools , lo que evita su implementación manual.

pylang
fuente
0

Una forma alternativa sin usar 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 claro y simple sin encadenar demasiados métodos o usar una lista larga de comprensión que puede ser difícil de leer.

Pie 'Oh' Pah
fuente
0

Una forma alternativa usando enumerateyisupper()

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']
El sexto sentido
fuente
0

Compartiendo lo que me vino a la mente cuando leí el post. Diferente de otras publicaciones.

strs = 'TheLongAndWindingRoad'

# grab index of uppercase letters in strs
start_idx = [i for i,j in enumerate(strs) if j.isupper()]

# create empty list
strs_list = []

# initiate counter
cnt = 1

for pos in start_idx:
    start_pos = pos

    # use counter to grab next positional element and overlook IndexeError
    try:
        end_pos = start_idx[cnt]
    except IndexError:
        continue

    # append to empty list
    strs_list.append(strs[start_pos:end_pos])

    cnt += 1
¿L.
fuente
0

La forma pitónica podría ser:

"".join([(" "+i if i.isupper() else i) for i in 'TheLongAndWindingRoad']).strip().split()
['The', 'Long', 'And', 'Winding', 'Road']

Funciona bien para Unicode, evitando re / re2.

"".join([(" "+i if i.isupper() else i) for i in 'СуперМаркетыПродажаКлиент']).strip().split()
['Супер', 'Маркеты', 'Продажа', 'Клиент']
usuario12114088
fuente
-1

Reemplace cada letra mayúscula 'L' en el dado con un espacio vacío más esa letra "L". Podemos hacer esto usando la comprensión de listas o podemos definir una función para hacerlo de la siguiente manera.

s = 'TheLongANDWindingRoad ABC A123B45'
''.join([char if (char.islower() or not char.isalpha()) else ' '+char for char in list(s)]).strip().split()
>>> ['The', 'Long', 'A', 'N', 'D', 'Winding', 'Road', 'A', 'B', 'C', 'A123', 'B45']

Si elige ir por una función, así es como.

def splitAtUpperCase(text):
    result = ""
    for char in text:
        if char.isupper():
            result += " " + char
        else:
            result += char
    return result.split()

En el caso del ejemplo dado:

print(splitAtUpperCase('TheLongAndWindingRoad')) 
>>>['The', 'Long', 'A', 'N', 'D', 'Winding', 'Road']

Pero la mayoría de las veces que dividimos una oración en letras mayúsculas, suele ocurrir que queremos mantener abreviaturas que suelen ser un flujo continuo de letras mayúsculas. El siguiente código ayudaría.

def splitAtUpperCase(s):
    for i in range(len(s)-1)[::-1]:
        if s[i].isupper() and s[i+1].islower():
            s = s[:i]+' '+s[i:]
        if s[i].isupper() and s[i-1].islower():
            s = s[:i]+' '+s[i:]
    return s.split()

splitAtUpperCase('TheLongANDWindingRoad')

>>> ['The', 'Long', 'AND', 'Winding', 'Road']

Gracias.

Samuel Nde
fuente
@MarkByers No sé por qué alguien votó en contra de mi respuesta, pero me encantaría que la vieras por mí. Agradecería sus comentarios.
Samuel Nde