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

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

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.


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


+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

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

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

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

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

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

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.


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.


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

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

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

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


@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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.