2010-02-17 10 views
54

Jaki jest pythonic sposób podziału ciągu przed wystąpieniami danego zestawu znaków?Dzielenie ciągów wielkimi literami

Na przykład, chcę podzielić 'TheLongAndWindingRoad' w każdym wystąpieniu wielkiej litery (ewentualnie z wyjątkiem pierwszego) i uzyskać ['The', 'Long', 'And', 'Winding', 'Road'].

Edycja: Należy również podzielić pojedynczych zdarzeń, tj od 'ABC' Chciałbym uzyskać ['A', 'B', 'C'].

Odpowiedz

78

Niestety, nie jest możliwe split on a zero-width match w języku Python. Ale można użyć re.findall zamiast:

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

Pamiętaj, że spowoduje to opuszczenie dowolnych znaków przed pierwszą wielką literą. "theLongAndWindingRoad" spowoduje, że ['Long', 'And', 'Winding', 'Road'] –

+6

@MarcSchulder: Jeśli potrzebujesz tego przypadku, po prostu użyj ''[a-zA-Z] [^ AZ] *' 'jako wyrażenie regularne. – knub

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

lub

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

Filtr jest całkowicie niepotrzebny i nic nie kupuje nad bezpośrednim podziałem regex z grupą przechwytywania: '[s dla s w re.compile (r" ([AZ] [^ AZ] *) "). Split (" TheLongAndWindingRoad ") jeśli s] 'daje' ['The', 'Long', 'And', 'Winding', 'Road'] ' – smci

+0

@smci: To użycie' filtru' jest takie samo jak zrozumienie listy z warunkiem. Czy masz coś przeciwko temu? – Gabe

+0

Wiem, że można go zastąpić zrozumieniem listy z warunkiem, ponieważ właśnie napisałem ten kod, a następnie skopiowałeś go. Oto trzy powody, dla których lista zrozumienie jest lepsze: a) * Czytelny idiom: * listowych są bardziej pythonic idiomu i czytać jaśniejszy od lewej do prawej, niż 'filtr (lambdaconditionfunc ...)' b) w Pythonie 3 'filter()' zwraca iterator. Więc nie będą całkowicie równoznaczne. c) Spodziewam się, że 'filter()' również będzie wolniejsze – 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'] 

Jeśli chcesz "It'sATest" podzielić na ["It's", 'A', 'Test'] zmiany wysokość rexeg do "[A-Z][a-z']*"

+0

+1: Po pierwsze, aby działało ABC. Zaktualizowałem też teraz swoją odpowiedź. –

+0

>>> re.findall ('[A-Z] [a-z] *', "To około 70% gospodarki") -----> ['It', 'Economy'] – ChristopheD

+0

@ ChristopheD. OP nie mówi, jak należy traktować znaki inne niż alfabetyczne. –

2

rozwiązania alternatywnego (jeśli nie lubię wyraźnych wyrażeń regularnych):

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

Odmianą @ChristopheD jest rozwiązanie

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

Fajna - działa również w przypadku znaków spoza alfabetu łacińskiego. Przedstawione tutaj rozwiązania regex tego nie robią. – AlexVhr

20

Poniżej przedstawiono alternatywne rozwiązanie wyrażenia regularnego. Problemem może być reprased „jak mogę wstawić spację przed każdą literą wielką, przed wykonaniem podziału”:

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

Ma to tę zaletę, zachowując wszystkie znaki spoza spacją, które większość innych rozwiązań nie.

2
src = 'TheLongAndWindingRoad' 
glue = ' ' 

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

Czy możesz dodać wyjaśnienie, dlaczego to jest dobre rozwiązanie problemu. –

+0

Przepraszam. Nie pamiętam ostatniego kroku – user3726655

0

Alternatywnym sposobem bez użycia regex lub wyliczyć:

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

myślę, że to jest jaśniejsze i prostsze bez łączenia zbyt wielu metod lub za pomocą długiej listy ze zrozumieniem, które mogą być trudne do odczytania.

0

Alternatywny sposób korzystania enumerate i isupper()

Kod:

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 

wyjściowa:

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

Innym bez regex i zdolność do utrzymania ciągły wielkie litery, jeśli chciał

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

Jest to możliwe za pomocą narzędzia 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'] 

Należy również podzielić pojedynczych zdarzeń, to znaczy od 'ABC' Chciałbym uzyskać ['A', 'B', 'C'].

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

more_itertools jest pakietem innych firm z ponad 60 przydatnych narzędzi w tym wdrożeń dla wszystkich oryginalnego itertools recipes, co zapobiega ich ręczne wykonanie.

Powiązane problemy