Розділіть рядок на великі літери


94

Який пітонічний спосіб розділити рядок перед появою заданого набору символів?

Наприклад, я хочу розділити 'TheLongAndWindingRoad' будь-яку велику літеру (можливо, крім першої) і отримати ['The', 'Long', 'And', 'Winding', 'Road'].

Редагувати: Він також повинен розділяти окремі випадки, тобто від 'ABC'я хотів би отримати ['A', 'B', 'C'].

Відповіді:


137

На жаль, розділити на збіг нульової ширини в Python неможливо . Але re.findallзамість цього ви можете використовувати :

>>> 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
Пам'ятайте, що це призведе до випадіння будь-яких символів перед першим великим символом. 'theLongAndWindingRoad' призведе до ['Long', 'And', 'Winding', 'Road']
Marc Schulder

14
@MarcSchulder: Якщо вам потрібна ця справа, просто використовуйте '[a-zA-Z][^A-Z]*'як регулярний вираз.
knub

Можна зробити те ж саме без великих літер?
Лоран Чезаро,

2
Для того, щоб розділити нижні регістрові слова на верблюдіprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant

32

Ось альтернативне рішення регулярного виразу. Проблему можна виправити як "як вставити пробіл перед кожною великою літерою перед тим, як робити розбиття":

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

Ця перевага полягає у збереженні всіх символів, що не є пробілами, чого немає в більшості інших рішень.


Чи можете ви пояснити, чому працює пробіл до \ 1? Це через метод розбиття чи це щось пов’язане із регулярним виразом?
Lax_Sam

розділити роздільник за замовчуванням на будь-який пробіл
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']

Якщо ви хочете "It'sATest"розділити, щоб ["It's", 'A', 'Test']змінити rexeg на"[A-Z][a-z']*"


+1: для першого, хто почав працювати ABC. Я також оновив свою відповідь зараз.
Mark Byers

>>> re.findall ('[AZ] [az] *', "Це близько 70% економіки") -----> ['It', 'Economy']
ChristopheD

@ChristopheD. У OP не сказано, як слід поводитись із символами, що не належать до алфавіту.
Джон Ла Рой,

1
істина, але цей поточний спосіб регулярного виразу також dropsусі звичайні (просто альфа-слова) слова, які не починаються з великої літери. Я сумніваюся, що це було наміром ОП.
ChristopheD

8

Варіація рішення @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
Приємний - це працює і з нелатинськими символами. Наведені тут рішення регулярних виразів цього не роблять.
AlexVhr

7

Використовуйте підстановку:

У Python 3.7 ви можете зробити це:

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

І це дає:

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

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

або

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

1
Фільтр абсолютно непотрібний і не купує вам нічого за прямий розподіл регулярних [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]['The', 'Long', 'And', 'Winding', 'Road']
виразів

1
@smci: Це використання filterте саме, що і розуміння списку з умовою. У вас є щось проти?
Гейб,

1
Я знаю, що його можна замінити розумінням списку з умовою, тому що я щойно опублікував цей код, тоді ви його скопіювали. Ось три причини, чому переважно розуміння списку: а) Розбірлива ідіома: розуміння списку є більш пітонічною ідіомою і читається чіткіше зліва направо, ніжfilter(lambdaconditionfunc, ...) b) у Python 3, filter()повертає ітератор. Тож вони не будуть повністю рівнозначними. в) Я сподіваюся, filter()це теж повільніше
smci

4
src = 'TheLongAndWindingRoad'
glue = ' '

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

1
Не могли б ви додати пояснення, чому це є гарним рішенням проблеми.
Матас Вайткевічус

Вибачте. Я забув останній крок
user3726655

Мені здається стислим, пітонічним і зрозумілим.

4

Я думаю, що кращою відповіддю може бути розділення рядка на слова, які не закінчуються великими літерами. Це впорається з випадком, коли рядок не починається з великої літери.

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

приклад:

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

2

Альтернативне рішення (якщо вам не подобаються явні регулярні вирази):

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

Ще один без регулярного виразу та можливості зберігати суміжні великі літери, якщо цього хочеться

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

Це можливо за допомогою more_itertools.split_beforeінструменту.

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

Він також повинен розділяти окремі випадки, тобто від 'ABC'я хотів би отримати ['A', 'B', 'C'].

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

more_itertools- це сторонній пакет із 60+ корисними інструментами, включаючи реалізації для всіх оригінальних рецептів itertools , що позбавляє можливості їх ручного впровадження.


0

Альтернативний спосіб без використання регулярного виразу або перерахування:

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

Я думаю, що це зрозуміліше та простіше, не зв’язуючи занадто багато методів або не використовуючи розуміння довгого списку, яке може бути важко прочитати.


0

Альтернативний спосіб використання enumerateтаisupper()

Код:

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

Вихід:

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

0

Поділившись тим, що мені спало на думку, коли я прочитав пост. На відміну від інших публікацій.

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

Пітонічним способом може бути:

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

Добре працює для Unicode, уникаючи повторного / повторного використання 2.

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

-1

Замініть кожну велику букву "L" у даному порожнім пробілом плюс ту букву "L". Ми можемо зробити це, використовуючи розуміння списку, або можна визначити функцію, яка робить це наступним чином.

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

Якщо ви вибрали функцію, ось як.

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

У випадку наведеного прикладу:

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

Але більшу частину часу, коли ми ділимо речення на великі літери, зазвичай буває, що ми хочемо зберегти скорочення, які, як правило, є безперервним потоком великих літер. Код нижче допоможе.

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

Дякую.


@MarkByers Я не знаю, чому хтось проголосував за мою відповідь, але я хотів би, щоб ти подивився на мене. Буду вдячний за ваш відгук.
Самуель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.