Как сделать 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())