Подтвердить что ты не робот

Как сделать расщепление 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?

Изменение: Сводка решения

Я протестировал все предоставленные решения с несколькими тестовыми примерами:

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

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

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

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

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

string:                 'dromedaryCase'
AplusKminus:            ['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'
AplusKminus:            ['ABC', 'Word', 'DEF']
casimir_et_hippolyte:   ['ABC', 'Word', 'DEF']
two_hundred_success:    ['ABC', 'Word', 'DEF']
kalefranz:              ['ABCWord', 'DEF']

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

4b9b3361

Ответ 1

Как объяснил re.split(), 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]

Ответ 2

Используйте re.sub() и split()

import re

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

Результат

'CamelCaseTest123' -> ['Camel', 'Case', 'Test123']
'CamelCaseXYZ' -> ['Camel', 'Case', 'XYZ']
'XYZCamelCase' -> ['XYZ', 'Camel', 'Case']
'XYZ' -> ['XYZ']
'IPAddress' -> ['IP', 'Address']

Ответ 3

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

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

Примечание: (?=[AZ]|$) может быть сокращено с помощью двойного отрицания (отрицательный прогноз с отрицательным классом символов): (?![^AZ])

Ответ 4

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

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

Увидев это:

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

становится ясно, почему раскол не работает должным образом. Модуль 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

Ответ 5

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

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)

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

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

Ответ 6

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

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

Ответ 7

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

def split_camel(text, char):
    if len(text) <= 1: # To avoid adding a wrong space in the beginning
        return text+char
    if char.isupper() and text[-1].islower(): # Regular Camel case
        return text + " " + char
    elif text[-1].isupper() and char.islower() and text[-2] != " ": # Detect Camel case in case of abbreviations
        return text[:-1] + " " + text[-1] + char
    else: # Do nothing part
        return text + char

text = "PathURLFinder"
text = reduce(split_camel, a, "")
print text
# prints "Path URL Finder"
print text.split(" ")
# prints "['Path', 'URL', 'Finder']"

РЕДАКТИРОВАТЬ: Как предложено, вот код, чтобы поместить функциональность в одну функцию.

def split_camel(text):
    def splitter(text, char):
        if len(text) <= 1: # To avoid adding a wrong space in the beginning
            return text+char
        if char.isupper() and text[-1].islower(): # Regular Camel case
            return text + " " + char
        elif text[-1].isupper() and char.islower() and text[-2] != " ": # Detect Camel case in case of abbreviations
            return text[:-1] + " " + text[-1] + char
        else: # Do nothing part
            return text + char
    converted_text = reduce(splitter, text, "")
    return converted_text.split(" ")

split_camel("PathURLFinder")
# prints ['Path', 'URL', 'Finder']

Ответ 8

Ввод более комплексного подхода. Он решает несколько вопросов, таких как числа, строки, начинающиеся со строчных букв, односимвольные слова и т.д.

def camel_case_split(identifier, remove_single_letter_words=False):
    """Parses CamelCase and Snake naming"""
    concat_words = re.split('[^a-zA-Z]+', identifier)

    def camel_case_split(string):
        bldrs = [[string[0].upper()]]
        string = string[1:]
        for idx, c in enumerate(string):
            if bldrs[-1][-1].islower() and c.isupper():
                bldrs.append([c])
            elif c.isupper() and (idx+1) < len(string) and string[idx+1].islower():
                bldrs.append([c])
            else:
                bldrs[-1].append(c)

        words = [''.join(bldr) for bldr in bldrs]
        words = [word.lower() for word in words]
        return words
    words = []
    for word in concat_words:
        if len(word) > 0:
            words.extend(camel_case_split(word))
    if remove_single_letter_words:
        subset_words = []
        for word in words:
            if len(word) > 1:
                subset_words.append(word)
        if len(subset_words) > 0:
            words = subset_words
    return words

Ответ 9

Я думаю, что ниже это оптимизм

Def count_word(): Return (re.findall('[AZ]? [Az] +, ввод (' введите свою строку))

Печать (count_word())

Ответ 10

Я обнаружил, что регулярное выражение сложно построить, сложно отладить и с непредсказуемой скоростью выполнения. Мне нравится использовать их в функции поиска/замены в моей IDE, но я стараюсь избегать их в программах.

Вот довольно простое решение на чистом питоне:

def camel_case_split(s):
    idx = [0] + [i for i, e in enumerate(s) if e.isupper()] + [len(s)] 
    return [s[x:y] for x, y in zip(idx, idx[1:]) if x < y]  

И некоторые тесты:

def test():
    TESTS = [
        ("CamelCaseWordT", ['Camel', 'Case', 'Word', 'T']),
        ("CamelCaseWordTa", ['Camel', 'Case', 'Word', 'Ta']),
        ("aCamelCaseWordTa", ['a', 'Camel', 'Case', 'Word', 'Ta']),
        ("aCamelCaseWordT", ['a', 'Camel', 'Case', 'Word', 'T']),
        ("Ta", ['Ta']),
        ("aT", ['a', 'T']),
        ("a", ['a']),
        ("T", ['T']),
        ("", []),
    ]
    for (q,a) in TESTS:
        assert camel_case_split(q) == a

if __name__ == "__main__":
    test()