Как сделать CamelCase Сплит в python

то, чего я пытался достичь, было что-то вроде этого:

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

поэтому я искал и нашел это идеальное регулярное выражение:

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

в качестве следующего логического шага я пробовал:

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

Почему это не работает, и как я могу получить результат от связанного вопроса в python?

Edit: резюме решения

я протестировал все предоставленные решения с помощью нескольких тестов случаи:

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

в итоге вы можете сказать, что решение @kalefranz не соответствует вопросу (см. Последний случай), а решение @casimir et hippolyte съедает одно пространство и тем самым нарушает идею о том, что разделение не должно изменять отдельные части. Единственное различие между оставшимися двумя альтернативами заключается в том, что мое решение возвращает список с пустой строкой на пустом строковом входе, а решение @200_success возвращает пустой список. Я не знаю как сообщество python стоит на этом вопросе, поэтому я говорю: я в порядке с любым из них. И поскольку решение 200_success проще, я принял его как правильный ответ.

7 ответов


как объяснил @nfs,re.split() никогда не разбивается на пустое совпадение шаблонов. Поэтому вместо разделения следует попытаться найти интересующие вас компоненты.

вот решение с помощью re.finditer() что имитирует расщепление:

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]

использовать re.sub() и split()

import re

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

результат

['Camel', 'Case', 'Test123']

большую часть времени, когда вам не нужно проверять формат строки, глобальное исследование проще, чем разделение (для того же результата):

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

возвращает

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

чтобы иметь дело с дромадером тоже, вы можете использовать:

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

Примечание: (?=[A-Z]|$) можно сократить, используя двойное отрицание (отрицательный lookahead с отрицаемым классом символов):(?![^A-Z])


на документация для языка Python re.split говорит:

обратите внимание, что split никогда не будет разбивать строку на пустое совпадение шаблонов.

увидев это:

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

становится понятно, почему раскол работает не так, как ожидалось. The reмодуль находит пустые совпадения, как и предусмотрено регулярным выражением.

поскольку в документации указано, что это не ошибка, а скорее предполагаемое поведение, вы должны работайте вокруг этого при попытке создать раскол верблюда:

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

Я просто наткнулся на этот случай и написал регулярное выражение для его решения. На самом деле, это должно работать для любой группы слов.

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)

ключ здесь lookahead на первом возможном случае. Он будет соответствовать (и сохранять) прописные слова перед заглавными:

assert RE_WORDS.findall('FOOBar') == ['FOO', 'Bar']

вот еще одно решение, которое требует меньше кода и никаких сложных регулярных выражений:

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]

редактировать

приведенный выше код содержит оптимизацию, которая позволяет избежать перестройки всей строки с каждым добавленным символом. Оставляя эту оптимизацию, более простая версия (с комментариями) может выглядеть как

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

Я думаю, что ниже optimim

Def count_word(): Возвращение (re.findall ('[A-Z]?[a-z]+', input ('пожалуйста, введите строку’))

печать(count_word())