Ce que j'essayais de réaliser, c'était quelque chose comme ceci:
>>> camel_case_split("CamelCaseXYZ")
['Camel', 'Case', 'XYZ']
>>> camel_case_split("XYZCamelCase")
['XYZ', 'Camel', 'Case']
J'ai donc cherché et trouvé ceci expression régulière parfaite :
(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])
Comme prochaine étape logique, j'ai essayé:
>>> re.split("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "CamelCaseXYZ")
['CamelCaseXYZ']
Pourquoi cela ne fonctionne-t-il pas et comment puis-je obtenir le résultat de la question liée en python?
Modifier: Résumé de la solution
J'ai testé toutes les solutions fournies avec quelques cas de test:
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']
En résumé, vous pourriez dire que la solution de @kalefranz ne correspond pas à la question (voir le dernier cas) et la solution de @casimir et hippolyte mange un seul espace, et viole ainsi l'idée qu'un fractionnement ne devrait pas changer les différentes parties. La seule différence entre les deux alternatives restantes est que ma solution renvoie une liste avec la chaîne vide sur une entrée de chaîne vide et la solution par @ 200_success renvoie une liste vide. Je ne sais pas comment la communauté python se tient sur ce problème, donc je dis: je suis d'accord avec l'un ou l'autre. Et puisque la solution de 200_success est plus simple, je l'ai acceptée comme la bonne réponse.
Comme l'a expliqué @AplusKminus, re.split()
ne se divise jamais sur une correspondance de modèle vide. Par conséquent, au lieu de diviser, vous devriez essayer de trouver les composants qui vous intéressent.
Voici une solution utilisant re.finditer()
qui émule le fractionnement:
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]
Utilisez re.sub()
et split()
import re
name = 'CamelCaseTest123'
splitted = re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name)).split()
Résultat
'CamelCaseTest123' -> ['Camel', 'Case', 'Test123']
'CamelCaseXYZ' -> ['Camel', 'Case', 'XYZ']
'XYZCamelCase' -> ['XYZ', 'Camel', 'Case']
'XYZ' -> ['XYZ']
'IPAddress' -> ['IP', 'Address']
La plupart du temps, lorsque vous n'avez pas besoin de vérifier le format d'une chaîne, une recherche globale est plus simple qu'une scission (pour le même résultat):
re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', 'CamelCaseXYZ')
retour
['Camel', 'Case', 'XYZ']
Pour gérer également le dromadaire, vous pouvez utiliser:
re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)', 'camelCaseXYZ')
Remarque: (?=[A-Z]|$)
peut être raccourci en utilisant une double négation (un lookahead négatif avec une classe de caractères niée): (?![^A-Z])
documentation pour le re.split
De python dit:
Notez que le fractionnement ne divisera jamais une chaîne sur une correspondance de modèle vide.
En voyant cela:
>>> re.findall("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "CamelCaseXYZ")
['', '']
il devient clair, pourquoi la scission ne fonctionne pas comme prévu. Le module re
trouve des correspondances vides, comme prévu par l'expression régulière.
Étant donné que la documentation indique qu'il ne s'agit pas d'un bogue, mais plutôt d'un comportement prévu, vous devez contourner cela lorsque vous essayez de créer une division de cas de chameau:
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
Je suis juste tombé sur ce cas et j'ai écrit une expression régulière pour le résoudre. Cela devrait fonctionner pour n'importe quel groupe de mots, en fait.
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)
La clé ici est le lookahead sur le premier cas possible. Il fera correspondre (et préservera) les mots en majuscules avant les mots en majuscule:
assert RE_WORDS.findall('FOOBar') == ['FOO', 'Bar']
Mettre en place une approche plus globale. Il prend en charge plusieurs problèmes tels que les nombres, les chaînes commençant par des minuscules, les mots d'une seule lettre, etc.
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
Voici une autre solution qui nécessite moins de code et aucune expression régulière compliquée:
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]
Le code ci-dessus contient une optimisation qui évite de reconstruire la chaîne entière avec chaque caractère ajouté. En laissant de côté cette optimisation, une version plus simple (avec commentaires) pourrait ressembler à
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
Je sais que la question a ajouté le tag de regex. Mais quand même, j'essaie toujours de rester le plus loin possible des regex. Voici donc ma solution sans 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']"
EDIT: Comme suggéré, voici le code pour mettre la fonctionnalité dans une seule fonction.
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']