Divida uma string em letras maiúsculas

94

Qual é a maneira pythônica de dividir uma string antes das ocorrências de um determinado conjunto de caracteres?

Por exemplo, desejo dividir 'TheLongAndWindingRoad' em qualquer ocorrência de uma letra maiúscula (possivelmente exceto a primeira) e obter ['The', 'Long', 'And', 'Winding', 'Road'].

Edit: Ele também deve dividir ocorrências únicas, ou seja, de 'ABC'que eu gostaria de obter ['A', 'B', 'C'].

Federico A. Ramponi
fonte

Respostas:

137

Infelizmente, não é possível dividir em uma correspondência de largura zero no Python. Mas você pode usar em re.findallvez disso:

>>> 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
fonte
13
Esteja ciente de que isso eliminará todos os caracteres antes do primeiro caractere maiúsculo. 'theLongAndWindingRoad' resultaria em ['Long', 'And', 'Winding', 'Road']
Marc Schulder
14
@MarcSchulder: Se você precisar desse caso, use apenas '[a-zA-Z][^A-Z]*'como regex.
knub de
É possível fazer o mesmo sem upercase?
Laurent Cesaro
2
Para dividir as palavras minúsculas de cameloprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant
32

Aqui está uma solução alternativa de regex. O problema pode ser reprogramado como "como faço para inserir um espaço antes de cada letra maiúscula, antes de fazer a divisão":

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

Isso tem a vantagem de preservar todos os caracteres que não sejam de espaço em branco, o que a maioria das outras soluções não faz.

Dave Kirby
fonte
Você pode explicar por que o espaço antes de \ 1 funciona? É por causa do método de divisão ou é algo relacionado ao regex?
Lax_Sam
delimitador de divisão padrão para qualquer string de espaço em branco
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 você quiser "It'sATest"dividir, ["It's", 'A', 'Test']mude o rexeg para"[A-Z][a-z']*"

John La Rooy
fonte
+1: Para primeiro fazer o ABC funcionar. Eu também atualizei minha resposta agora.
Mark Byers
>>> re.findall ('[AZ] [az] *', "É cerca de 70% da economia") -----> ['Isso', 'Economia']
ChristopheD
@ChristopheD. O OP não diz como os caracteres não alfa devem ser tratados.
John La Rooy
1
verdade, mas esta forma regex atual também dropstodas as palavras regulares (apenas alfa simples) que não começam com uma letra maiúscula. Duvido que fosse essa a intenção do OP.
ChristopheD
8

Uma variação da solução 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
fonte
2
Legal - isso funciona com caracteres não latinos também. As soluções regex mostradas aqui não.
AlexVhr
7

Use um olhar à frente:

No Python 3.7, você pode fazer isso:

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

E produz:

['the', 'Long', 'And', 'Winding', 'Road']
Endlisnis
fonte
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
fonte
1
O filtro é totalmente desnecessário e não compra nada além de uma divisão direta de regex com grupo de captura: [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]dando['The', 'Long', 'And', 'Winding', 'Road']
smci
1
@smci: Este uso de filteré o mesmo que a compreensão de lista com uma condição. Você tem algo contra isso?
Gabe
1
Eu sei que pode ser substituído por uma compreensão de lista com uma condição, porque acabei de postar esse código e você o copiou. Aqui estão três razões pelas quais a compreensão de lista é preferível: a) Expressão legível : as compreensões de lista são um idioma mais Pythônico e são mais claras da esquerda para a direita do que filter(lambdaconditionfunc, ...)b) em Python 3, filter()retorna um iterador. Portanto, eles não serão totalmente equivalentes. c) Espero que filter()seja mais lento também
smci 01 de
4
src = 'TheLongAndWindingRoad'
glue = ' '

result = ''.join(glue + x if x.isupper() else x for x in src).strip(glue).split(glue)
user3726655
fonte
1
Você poderia acrescentar uma explicação de por que esta é uma boa solução para o problema.
Matas Vaitkevicius
Eu sinto Muito. Esqueci a última etapa
user3726655
Parece conciso, pitônico e autoexplicativo, para mim.
4

Eu acho que uma resposta melhor pode ser para dividir a string em palavras que não terminam em uma capital. Isso resolveria o caso em que a string não começa com uma letra maiúscula.

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

exemplo:

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

Solução alternativa (se você não gosta de regexes explícitas):

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
fonte
1

Outro sem regex e a capacidade de manter letras maiúsculas contíguas, se desejado

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
fonte
1

Isso é possível com a more_itertools.split_beforeferramenta.

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

Ele também deve dividir as ocorrências únicas, ou seja, de 'ABC'Desejo obter ['A', 'B', 'C'].

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

more_itertoolsé um pacote de terceiros com mais de 60 ferramentas úteis, incluindo implementações para todas as receitas de itertools originais , o que elimina sua implementação manual.

pilang
fonte
0

Uma maneira alternativa sem usar regex ou enumerar:

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

Acho que é mais claro e simples, sem encadear muitos métodos ou usar uma longa lista de compreensão que pode ser difícil de ler.

Torta 'Oh' Pah
fonte
0

Uma forma alternativa usando enumerateeisupper()

Código:

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

Resultado:

['The', 'Long', 'And', 'Winding', 'Road']
The6thSense
fonte
0

Compartilhando o que me veio à mente quando li a postagem. Diferente de outras postagens.

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
Faça L.
fonte
0

A forma pitônica pode ser:

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

Funciona bem para Unicode, evitando re / re2.

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

Substitua todas as letras maiúsculas 'L' no dado com um espaço vazio mais a letra "L". Podemos fazer isso usando a compreensão de lista ou podemos definir uma função para fazer isso da seguinte maneira.

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 você escolher seguir uma função, veja como.

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

No caso do exemplo dado:

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

Mas na maioria das vezes que estamos dividindo uma frase em letras maiúsculas, geralmente queremos manter as abreviaturas que são tipicamente um fluxo contínuo de letras maiúsculas. O código abaixo ajudaria.

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

Obrigado.

Samuel Nde
fonte
@MarkByers Eu não sei por que alguém votou contra minha resposta, mas eu adoraria que você desse uma olhada para mim. Eu apreciaria seu feedback.
Samuel Nde