2010-02-17 17 views
54

Qual è il modo pitone per dividere una stringa prima delle occorrenze di un determinato set di caratteri?Dividere una stringa in lettere maiuscole

Ad esempio, voglio dividere 'TheLongAndWindingRoad' in ogni occorrenza di una lettera maiuscola (possibilmente tranne il primo), ed ottenere ['The', 'Long', 'And', 'Winding', 'Road'].

Edit: Si deve anche dividere singoli avvenimenti, cioè da 'ABC' mi piacerebbe ottenere ['A', 'B', 'C'].

risposta

78

Sfortunatamente non è possibile split on a zero-width match in Python. Ma è possibile utilizzare re.findall invece:

>>> 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

Attenzione che questo farà cadere qualsiasi carattere prima del primo carattere maiuscolo. 'theLongAndWindingRoad' risulterebbe in ['Long', 'And', 'Winding', 'Road'] –

+6

@MarcSchulder: Se hai bisogno di quel caso, usa '' [a-zA-Z] [^ AZ] * ' come la regex. – 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

Il filtro è totalmente inutile e non ti compra nulla su una divisione regolare diretta divisa con il gruppo di cattura: '[s per s in re.compile (r" ([AZ] [^ AZ] *) "). Split (" TheLongAndWindingRoad ") se s] 'dando' ['The', 'Long', 'And', 'Winding', 'Road'] ' – smci

+0

@smci: questo uso di' filter' è lo stesso della list comprehension con una condizione. Hai qualcosa contro di esso? – Gabe

+0

So che può essere sostituito con una list comprehension con una condizione, perché ho appena postato quel codice, quindi lo hai copiato. Qui ci sono tre ragioni per cui la lista di comprensione è preferibile: a) * linguaggio leggibile: * list comprehension sono un linguaggio più Pythonic e leggere più chiaro da sinistra a destra di 'filtro (lambdaconditionfunc, ...)' b) in Python 3, 'filter()' restituisce un iteratore. Quindi non saranno totalmente equivalenti. c) Prevedo che 'filter()' sia anche più 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'] 

Se si desidera "It'sATest" di dividere per ["It's", 'A', 'Test'] cambiamento della rexeg a "[A-Z][a-z']*"

+0

+1: per prima cosa funziona ABC. Ho anche aggiornato la mia risposta ora. –

+0

>>> re.findall ('[A-Z] [a-z] *', "È circa il 70% dell'economia") -----> ['It', 'Economy'] – ChristopheD

+0

@ChristopheD. L'OP non dice come trattare i caratteri non alfa. –

2

soluzione alternativa (se si non mi piace regex espliciti):

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 variante di soluzione @ChristopheD s'

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

Bello - questo funziona anche con caratteri non latini. Le soluzioni regex mostrate qui non lo fanno. – AlexVhr

20

Ecco una soluzione regex alternativa. Il problema può essere reprased come "come faccio a inserire uno spazio prima di ogni lettera maiuscola, prima di fare la scissione":

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

Questo ha il vantaggio di preservare tutti i caratteri non spazi bianchi, che la maggior parte delle altre soluzioni non lo fanno.

2
src = 'TheLongAndWindingRoad' 
glue = ' ' 

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

Potrebbe aggiungere una spiegazione al motivo per cui questa è una buona soluzione al problema. –

+0

Mi dispiace. Mi sono dimenticato l'ultimo passo – user3726655

0

Un modo alternativo senza usare espressioni regolari o enumerare:

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(' ') 

penso che sia più chiaro e più semplice, senza concatenamento troppi metodi o utilizzando una lunga lista di comprensione che può essere difficile da leggere.

0

Un modo alternativo utilizzando enumerate e isupper()

Codice:

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 

uscita:

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

altro senza regex e la capacità di mantenere maiuscolo contigui, se voleva

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

Questo è possibile con lo strumento 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'] 

Si deve anche dividere le occorrenze singoli, vale a dire da 'ABC' mi piacerebbe ottenere ['A', 'B', 'C'].

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

more_itertools è un pacchetto di terze parti con oltre 60 strumenti utili tra cui implementazioni per tutte dell'originale itertools recipes, che evita la loro attuazione manuale.

Problemi correlati