Разделить строку на прописные буквы


100

Каким питоническим способом разделить строку перед появлением данного набора символов?

Например, я хочу разделить 'TheLongAndWindingRoad' любое вхождение заглавной буквы (возможно, кроме первой) и получить ['The', 'Long', 'And', 'Winding', 'Road'].

Изменить: он также должен разделить отдельные вхождения, то есть от того, что 'ABC'я хотел бы получить ['A', 'B', 'C'].

Ответы:


142

К сожалению, в 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']

14
Помните, что при этом будут выпадать любые символы перед первым заглавным символом. 'theLongAndWindingRoad' приведет к ['Long', 'And', 'Winding', 'Road']
Марк Шульдер

15
@MarcSchulder: Если вам нужен этот случай, просто используйте его '[a-zA-Z][^A-Z]*'как регулярное выражение.
knub

Можно ли сделать то же самое без заглавных букв?
Laurent Cesaro

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

35

Вот альтернативное решение для регулярного выражения. Проблема может быть переформулирована как «как мне вставить пробел перед каждой прописной буквой перед разделением»:

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

разделитель по умолчанию - любая строка с
пробелами

@Lax_Sam подстановка регулярного выражения просто добавляет пробел перед любой заглавной буквой, а split () выбирает его
Витали,

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. Я также обновил свой ответ.
Марк Байерс

>>> re.findall ('[AZ] [az] *', «Это примерно 70% экономики») -----> ['It', 'Economy']
ChristopheD

@ChristopheD. OP не говорит, как следует относиться к не-альфа-символам.
Джон Ла Рой,

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

9

Вариант решения @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 03

7

Используйте просмотр вперед:

В Python 3.7 вы можете сделать это:

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

И это дает:

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

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

1
@smci: это использование filterаналогично пониманию списка с условием. Вы что-нибудь имеете против?
Гейб

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

5

Я думаю, что лучшим ответом может быть разделение строки на слова, не оканчивающиеся на заглавную букву. Это подойдет для случая, когда строка не начинается с заглавной буквы.

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

пример:

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

4
src = 'TheLongAndWindingRoad'
glue = ' '

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

1
Не могли бы вы объяснить, почему это хорошее решение проблемы.
Матас Вайткявичюс

Мне жаль. Я забыл последний шаг
user3726655 08

Мне это кажется лаконичным, питоническим и понятным.

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 , что исключает их ручную реализацию.


1

Питонический способ может быть:

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

Хорошо работает с Unicode, избегая re / re2.

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

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

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