私が達成しようとしていたことは、このようなものでした:
>>> 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による解決策は1つのスペースを消費するため、分割によって個々のパーツが変更されるべきではないという考えに違反します。残りの2つの選択肢の唯一の違いは、私のソリューションは空の文字列入力で空の文字列を含むリストを返し、@ 200_successによるソリューションは空のリストを返すことです。 pythonコミュニティがその問題をどのように支持しているかわからないので、私は言います:どちらでも構いません。200_successの解決法は簡単なので、正解として受け入れました。
@AplusKminusが説明したように、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' \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']
ほとんどの場合、文字列の形式を確認する必要はありませんが、グローバルな調査は分割よりも簡単です(同じ結果が得られます)。
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]|$)
は、二重否定(否定された文字クラスを持つ先読み)を使用して短縮できます:(?![^A-Z])
ドキュメント pythonのre.split
言う:
Splitは、空のパターンマッチで文字列を分割しないことに注意してください。
これを見たとき:
>>> re.findall("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "CamelCaseXYZ")
['', '']
スプリットが期待どおりに機能しない理由が明らかになります。 re
moduleは、正規表現で意図されているように、空の一致を検出します。
ドキュメントには、これはバグではなく、意図された動作であると記載されているため、ラクダケース分割を作成しようとすると、この問題を回避する必要があります。
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(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
以下は、より少ないコードで複雑な正規表現を必要としない別のソリューションです。
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
私は質問が正規表現のタグを追加したことを知っています。しかし、それでも、私は常に正規表現から可能な限り離れようとしています。したがって、正規表現を使用しない私のソリューションは次のとおりです。
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']