Diviser une chaîne en majuscules

94

Quelle est la manière pythonique de diviser une chaîne avant les occurrences d'un ensemble donné de caractères?

Par exemple, je veux diviser 'TheLongAndWindingRoad' à toute occurrence d'une lettre majuscule (éventuellement sauf la première) et obtenir ['The', 'Long', 'And', 'Winding', 'Road'].

Edit: Il devrait également diviser les occurrences uniques, c'est-à-dire que 'ABC'je voudrais obtenir ['A', 'B', 'C'].

Federico A. Ramponi
la source

Réponses:

137

Malheureusement, il n'est pas possible de diviser sur une correspondance de largeur nulle en Python. Mais vous pouvez utiliser à la re.findallplace:

>>> import re
>>> re.findall('[A-Z][^A-Z]*', 'TheLongAndWindingRoad')
['The', 'Long', 'And', 'Winding', 'Road']
>>> re.findall('[A-Z][^A-Z]*', 'ABC')
['A', 'B', 'C']
Mark Byers
la source
13
Attention, cela supprimera tous les caractères avant le premier caractère majuscule. 'theLongAndWindingRoad' entraînerait ['Long', 'And', 'Winding', 'Road']
Marc Schulder
14
@MarcSchulder: Si vous avez besoin de ce cas, utilisez simplement '[a-zA-Z][^A-Z]*'comme regex.
knub
Est-il possible de faire la même chose sans majuscules?
Laurent Cesaro
2
Afin de diviser les mots de cas de chameau inférieurprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant
32

Voici une solution de regex alternative. Le problème peut être reprasé comme "comment insérer un espace avant chaque lettre majuscule, avant de faire le fractionnement":

>>> s = "TheLongAndWindingRoad ABC A123B45"
>>> re.sub( r"([A-Z])", r" \1", s).split()
['The', 'Long', 'And', 'Winding', 'Road', 'A', 'B', 'C', 'A123', 'B45']

Cela présente l'avantage de conserver tous les caractères non blancs, ce que la plupart des autres solutions ne font pas.

Dave Kirby
la source
Pouvez-vous expliquer pourquoi l’espace avant \ 1 fonctionne? Est-ce à cause de la méthode de partage ou est-ce quelque chose lié à regex?
Lax_Sam
le délimiteur de division par défaut est une chaîne d'espaces blancs
CIsForCookies
20
>>> import re
>>> re.findall('[A-Z][a-z]*', 'TheLongAndWindingRoad')
['The', 'Long', 'And', 'Winding', 'Road']

>>> re.findall('[A-Z][a-z]*', 'SplitAString')
['Split', 'A', 'String']

>>> re.findall('[A-Z][a-z]*', 'ABC')
['A', 'B', 'C']

Si vous souhaitez "It'sATest"fractionner pour ["It's", 'A', 'Test']changer le rexeg en"[A-Z][a-z']*"

John La Rooy
la source
+1: Pour le premier à faire fonctionner ABC. J'ai également mis à jour ma réponse maintenant.
Mark Byers
>>> re.findall ('[AZ] [az] *', "Cela représente environ 70% de l'économie") -----> ['It', 'Economy']
ChristopheD
@ChristopheD. L'OP ne dit pas comment les caractères non alpha doivent être traités.
John La Rooy
1
vrai, mais cette regex actuelle utilise également dropstous les mots réguliers (juste alpha) qui ne commencent pas par une lettre majuscule. Je doute que ce fût l'intention du PO.
ChristopheD
8

Une variante de la solution de @ChristopheD

s = 'TheLongAndWindingRoad'

pos = [i for i,e in enumerate(s+'A') if e.isupper()]
parts = [s[pos[j]:pos[j+1]] for j in xrange(len(pos)-1)]

print parts
pwdyson
la source
2
Nice one - cela fonctionne aussi avec des caractères non latins. Les solutions regex présentées ici ne le font pas.
AlexVhr
7

Utilisez une anticipation:

Dans Python 3.7, vous pouvez faire ceci:

re.split('(?=[A-Z])', 'theLongAndWindingRoad')

Et cela donne:

['the', 'Long', 'And', 'Winding', 'Road']
Endlisnis
la source
5
import re
filter(None, re.split("([A-Z][^A-Z]*)", "TheLongAndWindingRoad"))

ou

[s for s in re.split("([A-Z][^A-Z]*)", "TheLongAndWindingRoad") if s]
Gabe
la source
1
Le filtre est totalement inutile et ne vous achète rien sur un partage regex direct avec groupe de capture: [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]donner['The', 'Long', 'And', 'Winding', 'Road']
smci
1
@smci: Cette utilisation de filterest la même que la compréhension de liste avec une condition. Avez-vous quelque chose contre?
Gabe
1
Je sais qu'il peut être remplacé par une compréhension de liste avec une condition, car je viens de publier ce code, puis vous l'avez copié. Voici trois raisons pour lesquelles la compréhension de liste est préférable: a) Idiome lisible: les compréhensions de liste sont un idiome plus pythonique et se lisent plus clairement de gauche à droite que filter(lambdaconditionfunc, ...)b) en Python 3, filter()renvoie un itérateur. Ils ne seront donc pas totalement équivalents. c) Je pense que filter()c'est aussi plus lent
smci
4
src = 'TheLongAndWindingRoad'
glue = ' '

result = ''.join(glue + x if x.isupper() else x for x in src).strip(glue).split(glue)
user3726655
la source
1
Pourriez-vous s'il vous plaît ajouter une explication pour expliquer pourquoi c'est une bonne solution au problème.
Matas Vaitkevicius
Je suis désolé. J'ai oublié la dernière étape
user3726655
Cela me semble concis, pythonique et explicite.
4

Je pense qu'une meilleure réponse pourrait être de diviser la chaîne en mots qui ne se terminent pas par une majuscule. Cela permettrait de gérer le cas où la chaîne ne commence pas par une majuscule.

 re.findall('.[^A-Z]*', 'aboutTheLongAndWindingRoad')

exemple:

>>> import re
>>> re.findall('.[^A-Z]*', 'aboutTheLongAndWindingRoadABC')
['about', 'The', 'Long', 'And', 'Winding', 'Road', 'A', 'B', 'C']
musaraigne
la source
2

Solution alternative (si vous n'aimez pas les expressions régulières explicites):

s = 'TheLongAndWindingRoad'

pos = [i for i,e in enumerate(s) if e.isupper()]

parts = []
for j in xrange(len(pos)):
    try:
        parts.append(s[pos[j]:pos[j+1]])
    except IndexError:
        parts.append(s[pos[j]:])

print parts
ChristopheD
la source
1

Un autre sans regex et la possibilité de garder des majuscules contiguës si vous le souhaitez

def split_on_uppercase(s, keep_contiguous=False):
    """

    Args:
        s (str): string
        keep_contiguous (bool): flag to indicate we want to 
                                keep contiguous uppercase chars together

    Returns:

    """

    string_length = len(s)
    is_lower_around = (lambda: s[i-1].islower() or 
                       string_length > (i + 1) and s[i + 1].islower())

    start = 0
    parts = []
    for i in range(1, string_length):
        if s[i].isupper() and (not keep_contiguous or is_lower_around()):
            parts.append(s[start: i])
            start = i
    parts.append(s[start:])

    return parts

>>> split_on_uppercase('theLongWindingRoad')
['the', 'Long', 'Winding', 'Road']
>>> split_on_uppercase('TheLongWindingRoad')
['The', 'Long', 'Winding', 'Road']
>>> split_on_uppercase('TheLongWINDINGRoadT', True)
['The', 'Long', 'WINDING', 'Road', 'T']
>>> split_on_uppercase('ABC')
['A', 'B', 'C']
>>> split_on_uppercase('ABCD', True)
['ABCD']
>>> split_on_uppercase('')
['']
>>> split_on_uppercase('hello world')
['hello world']
Totoro
la source
1

Ceci est possible avec l' more_itertools.split_beforeoutil.

import more_itertools as mit


iterable = "TheLongAndWindingRoad"
[ "".join(i) for i in mit.split_before(iterable, pred=lambda s: s.isupper())]
# ['The', 'Long', 'And', 'Winding', 'Road']

Il doit également diviser les occurrences uniques, c'est-à-dire celles 'ABC'que j'aimerais obtenir ['A', 'B', 'C'].

iterable = "ABC"
[ "".join(i) for i in mit.split_before(iterable, pred=lambda s: s.isupper())]
# ['A', 'B', 'C']

more_itertoolsest un package tiers avec plus de 60 outils utiles, y compris des implémentations pour toutes les recettes itertools originales , ce qui évite leur implémentation manuelle.

pylang
la source
0

Une autre façon sans utiliser regex ou enumerate:

word = 'TheLongAndWindingRoad'
list = [x for x in word]

for char in list:
    if char != list[0] and char.isupper():
        list[list.index(char)] = ' ' + char

fin_list = ''.join(list).split(' ')

Je pense que c'est plus clair et plus simple sans enchaîner trop de méthodes ou en utilisant une longue compréhension de liste qui peut être difficile à lire.

Tarte 'Oh' Pah
la source
0

Une autre façon d'utiliser enumerateetisupper()

Code:

strs = 'TheLongAndWindingRoad'
ind =0
count =0
new_lst=[]
for index, val in enumerate(strs[1:],1):
    if val.isupper():
        new_lst.append(strs[ind:index])
        ind=index
if ind<len(strs):
    new_lst.append(strs[ind:])
print new_lst

Production:

['The', 'Long', 'And', 'Winding', 'Road']
The6thSense
la source
0

Partager ce qui m'est venu à l'esprit lorsque j'ai lu l'article. Différent des autres messages.

strs = 'TheLongAndWindingRoad'

# grab index of uppercase letters in strs
start_idx = [i for i,j in enumerate(strs) if j.isupper()]

# create empty list
strs_list = []

# initiate counter
cnt = 1

for pos in start_idx:
    start_pos = pos

    # use counter to grab next positional element and overlook IndexeError
    try:
        end_pos = start_idx[cnt]
    except IndexError:
        continue

    # append to empty list
    strs_list.append(strs[start_pos:end_pos])

    cnt += 1
Faites L.
la source
0

La voie pythonique pourrait être:

"".join([(" "+i if i.isupper() else i) for i in 'TheLongAndWindingRoad']).strip().split()
['The', 'Long', 'And', 'Winding', 'Road']

Fonctionne bien pour Unicode, évitant re / re2.

"".join([(" "+i if i.isupper() else i) for i in 'СуперМаркетыПродажаКлиент']).strip().split()
['Супер', 'Маркеты', 'Продажа', 'Клиент']
user12114088
la source
-1

Remplacez chaque lettre majuscule «L» dans le donné par un espace vide plus cette lettre «L». Nous pouvons le faire en utilisant la compréhension de liste ou nous pouvons définir une fonction pour le faire comme suit.

s = 'TheLongANDWindingRoad ABC A123B45'
''.join([char if (char.islower() or not char.isalpha()) else ' '+char for char in list(s)]).strip().split()
>>> ['The', 'Long', 'A', 'N', 'D', 'Winding', 'Road', 'A', 'B', 'C', 'A123', 'B45']

Si vous choisissez de passer par une fonction, voici comment procéder.

def splitAtUpperCase(text):
    result = ""
    for char in text:
        if char.isupper():
            result += " " + char
        else:
            result += char
    return result.split()

Dans le cas de l'exemple donné:

print(splitAtUpperCase('TheLongAndWindingRoad')) 
>>>['The', 'Long', 'A', 'N', 'D', 'Winding', 'Road']

Mais la plupart du temps que nous divisons une phrase en majuscules, nous souhaitons généralement conserver des abréviations qui sont généralement un flux continu de lettres majuscules. Le code ci-dessous aiderait.

def splitAtUpperCase(s):
    for i in range(len(s)-1)[::-1]:
        if s[i].isupper() and s[i+1].islower():
            s = s[:i]+' '+s[i:]
        if s[i].isupper() and s[i-1].islower():
            s = s[:i]+' '+s[i:]
    return s.split()

splitAtUpperCase('TheLongANDWindingRoad')

>>> ['The', 'Long', 'AND', 'Winding', 'Road']

Merci.

Samuel Nde
la source
@MarkByers Je ne sais pas pourquoi quelqu'un a voté contre ma réponse, mais j'aimerais que vous jetiez un coup d'œil à ma réponse. J'apprécierais vos commentaires.
Samuel Nde