Suddividi una stringa in lettere maiuscole


94

Qual è il modo pitonico per dividere una stringa prima delle occorrenze di un dato insieme di caratteri?

Ad esempio, voglio dividere 'TheLongAndWindingRoad' in qualsiasi occorrenza di una lettera maiuscola (possibilmente tranne la prima) e ottenere ['The', 'Long', 'And', 'Winding', 'Road'].

Modifica: dovrebbe anche dividere le singole occorrenze, cioè da 'ABC'vorrei ottenere ['A', 'B', 'C'].

Risposte:


137

Sfortunatamente non è possibile dividere su una corrispondenza di larghezza zero in Python. Ma puoi usare re.findallinvece:

>>> 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
Attenzione che questo lascerà cadere tutti i caratteri prima del primo carattere maiuscolo. "theLongAndWindingRoad" risulterebbe in ["Long", "And", "Winding", "Road"]
Marc Schulder

14
@ MarcSchulder: Se hai bisogno di quel caso, usalo '[a-zA-Z][^A-Z]*'come regex.
Knub

È possibile fare lo stesso senza le maiuscole?
Laurent Cesaro

2
Per dividere le parole minuscole dei cammelliprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant

32

Ecco una soluzione regex alternativa. Il problema può essere riformulato come "come faccio a inserire uno spazio prima di ogni lettera maiuscola, prima di fare la divisione":

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

Ciò ha il vantaggio di preservare tutti i caratteri diversi dagli spazi, cosa che la maggior parte delle altre soluzioni non fa.


Puoi spiegare perché lo spazio prima di \ 1 funziona? È a causa del metodo split o è qualcosa correlato alla regex?
Lax_Sam

Il delimitatore di divisione ha come impostazione predefinita qualsiasi stringa di spazi bianchi
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']

Se vuoi "It'sATest"dividere per ["It's", 'A', 'Test']cambiare il rexeg in"[A-Z][a-z']*"


+1: per far funzionare prima l'ABC. Ho anche aggiornato la mia risposta ora.
Mark Byers

>>> re.findall ('[AZ] [az] *', "È circa il 70% dell'economia") -----> ['It', 'Economy']
ChristopheD

@ChristopheD. L'OP non dice come trattare i caratteri non alfabetici.
John La Rooy

1
vero, ma questo modo regex corrente anche dropstutte le parole normali (semplicemente alfa) che non iniziano con una lettera maiuscola. Dubito che questa fosse l'intenzione dell'OP.
ChristopheD

8

Una variazione sulla soluzione di @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
Bello: funziona anche con caratteri non latini. Le soluzioni regex mostrate qui non lo fanno.
AlexVhr

7

Usa uno sguardo in avanti:

In Python 3.7, puoi farlo:

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

E produce:

['the', 'Long', 'And', 'Winding', 'Road']

5
import re
filter(None, re.split("([A-Z][^A-Z]*)", "TheLongAndWindingRoad"))

o

[s for s in re.split("([A-Z][^A-Z]*)", "TheLongAndWindingRoad") if s]

1
Il filtro è totalmente inutile e non ti compra nulla rispetto a una divisione regex diretta con gruppo di cattura: [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]dando['The', 'Long', 'And', 'Winding', 'Road']
smci

1
@smci: questo utilizzo di filterè lo stesso della comprensione dell'elenco con una condizione. Hai qualcosa contro di esso?
Gabe

1
So che può essere sostituito con una lista di comprensione con una condizione, perché ho appena pubblicato quel codice, poi l'hai copiato. Ecco tre motivi per cui è preferibile la comprensione della lista: a) idioma leggibile : le comprensioni delle liste sono un idioma più pitonico e sono più chiare da sinistra a destra rispetto a filter(lambdaconditionfunc, ...)b) in Python 3, filter()restituisce un iteratore. Quindi non saranno totalmente equivalenti. c) Mi aspetto che filter()sia anche più lento
smci

4
src = 'TheLongAndWindingRoad'
glue = ' '

result = ''.join(glue + x if x.isupper() else x for x in src).strip(glue).split(glue)

1
Potresti aggiungere una spiegazione del motivo per cui questa è una buona soluzione al problema.
Matas Vaitkevicius

Mi dispiace. Ho dimenticato l'ultimo passaggio
user3726655

Mi sembra conciso, pitonico e autoesplicativo.

4

Penso che una risposta migliore potrebbe essere quella di dividere la stringa in parole che non finiscono in maiuscolo. Questo gestirà il caso in cui la stringa non inizia con una lettera maiuscola.

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

esempio:

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

2

Soluzione alternativa (se non ti piacciono le espressioni regolari esplicite):

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 altro senza regex e la possibilità di mantenere le maiuscole contigue se lo si desidera

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

Questo è possibile con lo more_itertools.split_beforestrumento.

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

Dovrebbe anche dividere le singole occorrenze, cioè da 'ABC'mi piacerebbe ottenere ['A', 'B', 'C'].

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

more_itertoolsè un pacchetto di terze parti con oltre 60 strumenti utili, comprese le implementazioni per tutte le ricette originali di itertools , che evita la loro implementazione manuale.


0

Un modo alternativo senza usare regex o enumerare:

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(' ')

Penso che sia più chiaro e più semplice senza concatenare troppi metodi o utilizzare una lunga lista di comprensione che può essere difficile da leggere.


0

Un modo alternativo utilizzando enumerateeisupper()

Codice:

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

Produzione:

['The', 'Long', 'And', 'Winding', 'Road']

0

Condividendo quello che mi è venuto in mente quando ho letto il post. Diverso dagli altri post.

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

Il modo pitonico potrebbe essere:

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

Funziona bene per Unicode, evitando re / re2.

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

-1

Sostituisci ogni lettera maiuscola "L" nel dato con uno spazio vuoto più la lettera "L". Possiamo farlo usando la comprensione delle liste o possiamo definire una funzione per farlo come segue.

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

Se scegli di utilizzare una funzione, ecco come.

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

Nel caso dell'esempio fornito:

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

Ma la maggior parte delle volte che dividiamo una frase in lettere maiuscole, di solito è il caso che vogliamo mantenere le abbreviazioni che sono tipicamente un flusso continuo di lettere maiuscole. Il codice seguente aiuterebbe.

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

Grazie.


@MarkByers Non so perché qualcuno ha votato la mia risposta, ma mi piacerebbe che tu dessi un'occhiata per me. Apprezzerei il tuo feedback.
Samuel Nde
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.