Jak zrobić CamelCase split w Pythonie

To, co chciałem osiągnąć, było coś takiego:

>>> camel_case_split("CamelCaseXYZ")
['Camel', 'Case', 'XYZ']
>>> camel_case_split("XYZCamelCase")
['XYZ', 'Camel', 'Case']

Więc szukałem i znalazłem to doskonałe Wyrażenie regularne :

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])

Jako kolejny logiczny krok próbowałem:

>>> re.split("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "CamelCaseXYZ")
['CamelCaseXYZ']

Dlaczego to nie działa i jak Mogę uzyskać wynik z połączonego pytania w Pythonie?

Edit: podsumowanie rozwiązania

Przetestowałem wszystkie dostarczone rozwiązania kilkoma przypadkami testowymi:

string:                 ''
nfs:                    ['']
casimir_et_hippolyte:   []
two_hundred_success:    []
kalefranz:              string index out of range # with modification: either [] or ['']

string:                 ' '
nfs:                    [' ']
casimir_et_hippolyte:   []
two_hundred_success:    [' ']
kalefranz:              [' ']

string:                 'lower'
all algorithms:         ['lower']

string:                 'UPPER'
all algorithms:         ['UPPER']

string:                 'Initial'
all algorithms:         ['Initial']

string:                 'dromedaryCase'
nfs:                    ['dromedary', 'Case']
casimir_et_hippolyte:   ['dromedary', 'Case']
two_hundred_success:    ['dromedary', 'Case']
kalefranz:              ['Dromedary', 'Case'] # with modification: ['dromedary', 'Case']

string:                 'CamelCase'
all algorithms:         ['Camel', 'Case']

string:                 'ABCWordDEF'
nfs:                    ['ABC', 'Word', 'DEF']
casimir_et_hippolyte:   ['ABC', 'Word', 'DEF']
two_hundred_success:    ['ABC', 'Word', 'DEF']
kalefranz:              ['ABCWord', 'DEF']

W podsumowaniu można powiedzieć, że rozwiązanie przez @ kalefranz robi nie pasuje do pytania (patrz ostatni przypadek) i rozwiązanie przez @ casimir et hippolyte zjada pojedynczą przestrzeń, a tym samym narusza ideę, że podział nie powinien zmieniać poszczególnych części. Jedyną różnicą pomiędzy pozostałymi dwoma alternatywami jest to, że moje rozwiązanie zwraca listę z pustym łańcuchem na pustym łańcuchu wejściowym, a rozwiązanie @200_success zwraca pustą listę. Nie wiem, jak społeczność Pythona stoi w tej kwestii, więc mówię: nie mam nic przeciwko żadnej z nich. I od Rozwiązanie 200_success jest prostsze, zaakceptowałem je jako poprawną odpowiedź.

Author: nfs, 2015-04-28

7 answers

Jak wyjaśnił @nfs, re.split() nigdy nie dzieli się na puste dopasowanie wzorca. Dlatego zamiast dzielić, powinieneś spróbować znaleźć komponenty, które Cię interesują.

Oto rozwiązanie za pomocą re.finditer(), które emuluje dzielenie:

def camel_case_split(identifier):
    matches = finditer('.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', identifier)
    return [m.group(0) for m in matches]
 21
Author: 200_success,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-17 21:46:18

Użyj re.sub() i split()

import re

name = 'CamelCaseTest123'
splitted = re.sub('(?!^)([A-Z][a-z]+)', r' \1', name).split()

Wynik

['Camel', 'Case', 'Test123']
 9
Author: Jossef Harush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-08 08:26:24

W większości przypadków, gdy nie trzeba sprawdzać formatu ciągu, globalne badanie jest prostsze niż podział (dla tego samego wyniku):

re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', 'CamelCaseXYZ')

Zwraca

['Camel', 'Case', 'XYZ']

Do czynienia z dromedary zbyt, można użyć:

re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)', 'camelCaseXYZ')

Uwaga: (?=[A-Z]|$) można skrócić za pomocą podwójnej negacji (negatywnego spojrzenia z negowaną klasą znaków): (?![^A-Z])

 6
Author: Casimir et Hippolyte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-02 21:46:45

Dokumentacja dla Pythona re.split mówi:

Zauważ, że split nigdy nie podzieli łańcucha na puste dopasowanie wzorca.

Widząc to:

>>> re.findall("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "CamelCaseXYZ")
['', '']

Staje się jasne, dlaczego podział nie działa zgodnie z oczekiwaniami. Moduł reznajduje puste dopasowania, tak jak jest to zamierzone przez wyrażenie regularne.

Ponieważ dokumentacja stwierdza, że nie jest to błąd, ale raczej zamierzone zachowanie, musisz obejść to, próbując stworzyć wielbłąda podział przypadku:

def camel_case_split(identifier):
    matches = finditer('(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])', identifier)
    split_string = []
    # index of beginning of slice
    previous = 0
    for match in matches:
        # get slice
        split_string.append(identifier[previous:match.start()])
        # advance index
        previous = match.start()
    # get remaining string
    split_string.append(identifier[previous:])
    return split_string
 3
Author: nfs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-29 13:48:32

Natknąłem się na tę sprawę i napisałem Wyrażenie regularne, aby ją rozwiązać. To powinno zadziałać dla każdej grupy słów.

RE_WORDS = re.compile(r'''
    # Find words in a string. Order matters!
    [A-Z]+(?=[A-Z][a-z]) |  # All upper case before a capitalized word
    [A-Z]?[a-z]+ |  # Capitalized words / all lower case
    [A-Z]+ |  # All upper case
    \d+  # Numbers
''', re.VERBOSE)

Kluczem jest lookahead w pierwszym możliwym przypadku. Dopasuje (i zachowa) wielkie słowa przed wielkimi literami:

assert RE_WORDS.findall('FOOBar') == ['FOO', 'Bar']
 2
Author: emyller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-06 16:32:24

Oto inne rozwiązanie, które wymaga mniej kodu i nie wymaga skomplikowanych wyrażeń regularnych:

def camel_case_split(string):
    bldrs = [[string[0].upper()]]
    for c in string[1:]:
        if bldrs[-1][-1].islower() and c.isupper():
            bldrs.append([c])
        else:
            bldrs[-1].append(c)
    return [''.join(bldr) for bldr in bldrs]

Edytuj

Powyższy kod zawiera optymalizację, która pozwala uniknąć przebudowy całego ciągu Z Każdym dołączonym znakiem. Pomijając tę optymalizację, prostsza wersja (z komentarzami) może wyglądać tak:

def camel_case_split2(string):
    # set the logic for creating a "break"
    def is_transition(c1, c2):
      return c1.islower() and c2.isupper()

    # start the builder list with the first character
    # enforce upper case
    bldr = [string[0].upper()]
    for c in string[1:]:
        # get the last character in the last element in the builder
        # note that strings can be addressed just like lists
        previous_character = bldr[-1][-1]
        if is_transition(previous_character, c):
            # start a new element in the list
            bldr.append(c)
        else:
            # append the character to the last string
            bldr[-1] += c
    return bldr
 0
Author: kalefranz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-28 12:46:24

Myślę, że poniżej jest optimim

Def count_word(): Powrót (re.findall ('[a-Z]?[A-z]+', input ('please enter your string'))

Print (count_word ())

 -1
Author: Ahmoody,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-01 14:55:15