Как сделать расщепление 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 проще, я принял его как правильный ответ.
Ответы
Ответ 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()