Podziel ciąg na wielkie litery


101

Jaki jest pythonowy sposób dzielenia łańcucha przed wystąpieniami danego zestawu znaków?

Na przykład chcę podzielić 'TheLongAndWindingRoad' w każdym przypadku wystąpienia dużej litery (prawdopodobnie z wyjątkiem pierwszej) i uzyskać ['The', 'Long', 'And', 'Winding', 'Road'].

Edycja: Powinien również podzielić pojedyncze wystąpienia, tj. 'ABC'Z chciałbym uzyskać ['A', 'B', 'C'].

Odpowiedzi:


143

Niestety w Pythonie nie można podzielić na podstawie dopasowania o zerowej szerokości . Ale możesz re.findallzamiast tego użyć :

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

14
Pamiętaj, że spowoduje to usunięcie wszystkich znaków przed pierwszą wielką literą. „theLongAndWindingRoad” dałoby w wyniku [„Long”, „And”, „Winding”, „Road”]
Marc Schulder

16
@MarcSchulder: Jeśli potrzebujesz tej wielkości, użyj po prostu '[a-zA-Z][^A-Z]*'jako wyrażenia regularnego.
knub

Czy można to zrobić bez upercase?
Laurent Cesaro

4
Aby podzielić małe słowa z wielbłądówprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant

35

Oto alternatywne rozwiązanie regex. Problem można powtórzyć w postaci „jak wstawić spację przed każdą wielką literą 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ę, że zachowuje wszystkie znaki inne niż białe znaki, czego nie ma większość innych rozwiązań.


Czy możesz wyjaśnić, dlaczego działa spacja przed \ 1? Czy to z powodu metody podziału, czy jest to coś związanego z wyrażeniem regularnym?
Lax_Sam

separator podziału domyślnie
przyjmuje

@Lax_Sam podstawienie wyrażenia regularnego po prostu dodaje spację przed dowolną wielką literą, a
funkcja

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

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


+1: Po pierwsze, aby ABC działała. Zaktualizowałem też teraz moją odpowiedź.
Mark Byers,

>>> re.findall ('[AZ] [az] *', "To około 70% gospodarki") -----> ['It', 'Economy']
ChristopheD

@ChristopheD. OP nie mówi, jak należy traktować znaki inne niż alfa.
John La Rooy,

1
prawda, ale to obecne wyrażenie dropsregularne oznacza również wszystkie zwykłe (tylko zwykłe alfa) słowa, które nie rozpoczynają się od dużej litery. Wątpię, żeby taki był zamiar PO.
ChristopheD,

9

Odmiana rozwiązania @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

2
Niezły - działa to również w przypadku znaków spoza alfabetu łacińskiego. Przedstawione tutaj rozwiązania regex nie.
AlexVhr

7

Użyj lookahead:

W Pythonie 3.7 możesz to zrobić:

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

I daje:

['the', 'Long', 'And', 'Winding', 'Road']

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

1
Filtr jest całkowicie niepotrzebny i nie kupuje nic poza bezpośrednim podziałem [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]['The', 'Long', 'And', 'Winding', 'Road']
wyrażeń

1
@smci: To użycie filterjest takie samo jak rozumienie listy z warunkiem. Czy masz coś przeciwko temu?
Gabe

1
Wiem, że można to zastąpić zrozumieniem listy z warunkiem, ponieważ właśnie wysłałem ten kod, a następnie go skopiowałeś. Oto trzy powody, dla których rozumienie list jest lepsze: a) Czytelny idiom: listy składane są bardziej Pythonowym idiomem i czyta się jaśniej od lewej do prawej niż filter(lambdaconditionfunc, ...)b) w Pythonie 3 filter()zwraca iterator. Więc nie będą one całkowicie równoważne. c) Spodziewam się, że filter()też wolniej
smci

5

Myślę, że lepszą odpowiedzią może być podzielenie łańcucha na słowa, które nie kończą się wielką literą. Byłoby to obsługiwane w przypadku, gdy ciąg nie zaczyna się od dużej litery.

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

przykład:

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

4
src = 'TheLongAndWindingRoad'
glue = ' '

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

1
Czy mógłbyś dodać wyjaśnienie, dlaczego jest to dobre rozwiązanie problemu.
Matas Vaitkevicius,

Przepraszam. Zapomniałem ostatniego kroku
user3726655

Wydaje mi się zwięzłe, pytoniczne i oczywiste.

2

Alternatywne rozwiązanie (jeśli nie lubisz jawnych 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

1

Kolejny bez wyrażenia regularnego i możliwość zachowania ciągłych wielkich liter, jeśli jest taka potrzeba

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

1

Jest to możliwe dzięki more_itertools.split_beforenarzędziu.

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

Powinien też rozdzielać pojedyncze wystąpienia, tj. 'ABC'Z chciałbym uzyskać ['A', 'B', 'C'].

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

more_itertoolsto pakiet innej firmy z ponad 60 użytecznymi narzędziami, w tym implementacjami dla wszystkich oryginalnych receptur itertools , co zapobiega ich ręcznej implementacji.


1

Pythonic może wyglądać następująco:

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

Działa dobrze dla Unicode, unikając ponownego / re2.

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

0

Alternatywny sposób bez użycia wyrażenia regularnego lub wyliczenia:

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 jest to jaśniejsze i prostsze bez łączenia w łańcuch zbyt wielu metod lub używania długiej listy, która może być trudna do odczytania.


0

Alternatywny sposób używania enumerateiisupper()

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

Wynik:

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

0

Dzielenie się tym, co przyszło mi do głowy, kiedy czytałem post. Różni się od innych postów.

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

-1

Zastąp każdą wielką literę „L” w podanym pustą spacją plus tę literę „L”. Możemy to zrobić za pomocą funkcji list złożonych lub możemy zdefiniować funkcję wykonującą to w następujący sposób.

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

Jeśli zdecydujesz się skorzystać z funkcji, oto jak to zrobić.

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

W przypadku podanego przykładu:

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

Ale w większości przypadków, gdy dzielimy zdanie na duże litery, zwykle jest tak, że chcemy zachować skróty, które zazwyczaj są ciągłym ciągiem wielkich liter. Poniższy kod mógłby pomóc.

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

Dzięki.


@MarkByers Nie wiem, dlaczego ktoś przegłosował moją odpowiedź, ale chciałbym, żebyś spojrzał na to za mnie. Byłbym wdzięczny za twoją opinię.
Samuel Nde
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.