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'].

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']

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.


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']*"


+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

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']

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]

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)

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']

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

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']

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.


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.


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']

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

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()
['Супер', 'Маркеты', 'Продажа', 'Клиент']

-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.


@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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.