Pisahkan string menjadi huruf besar


100

Apa cara pythonic untuk memisahkan string sebelum kemunculan sekumpulan karakter tertentu?

Misalnya, saya ingin membagi 'TheLongAndWindingRoad' setiap kejadian huruf besar (mungkin kecuali yang pertama), dan mendapatkan ['The', 'Long', 'And', 'Winding', 'Road'].

Sunting: Ini juga harus membagi kejadian tunggal, yaitu dari 'ABC'saya ingin mendapatkan ['A', 'B', 'C'].

Jawaban:


142

Sayangnya, tidak mungkin untuk membagi pertandingan dengan lebar nol dengan Python. Tapi Anda bisa menggunakan re.findallsebagai gantinya:

>>> 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
Berhati-hatilah karena ini akan menjatuhkan karakter apa pun sebelum karakter kapital pertama. 'theLongAndWindingRoad' akan menghasilkan ['Long', 'And', 'Winding', 'Road']
Marc Schulder

15
@MarcSchulder: Jika Anda membutuhkan kasus itu, gunakan saja '[a-zA-Z][^A-Z]*'sebagai regex.
knub

Apakah mungkin untuk melakukan hal yang sama tanpa upercase?
Laurent Cesaro

4
Untuk membagi kata huruf kecil untaprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant

34

Berikut adalah solusi regex alternatif. Masalahnya dapat dirumuskan ulang sebagai "bagaimana cara menyisipkan spasi sebelum setiap huruf besar, sebelum melakukan pemisahan":

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

Ini memiliki keuntungan dalam mempertahankan semua karakter non-spasi, yang kebanyakan solusi lain tidak.


Bisakah Anda menjelaskan mengapa spasi sebelum \ 1 berfungsi? Apakah karena metode split atau ada yang berhubungan dengan regex?
Lax_Sam

membagi pembatas default ke string spasi kosong
CIsForCookies

@Lax_Sam substitusi regex hanya menambahkan spasi sebelum huruf kapital apa pun, dan split () mengambilnya
vitaly

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

Jika Anda ingin "It'sATest"membagi untuk ["It's", 'A', 'Test']mengubah rexeg menjadi"[A-Z][a-z']*"


+1: Untuk yang pertama membuat ABC berfungsi. Saya juga telah memperbarui jawaban saya sekarang.
Mark Byers

>>> re.findall ('[AZ] [az] *', "Ini tentang 70% dari Ekonomi") -----> ['It', 'Economy']
ChristopheD

@Tokopedia OP tidak mengatakan bagaimana karakter non-alfa harus diperlakukan.
John La Rooy

1
benar, tetapi cara regex saat ini juga dropssemua kata biasa (hanya alfa biasa) yang tidak dimulai dengan huruf besar. Saya ragu itu maksud OP.
ChristopheD

9

Variasi solusi @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
Bagus - ini juga berfungsi dengan karakter non-Latin. Solusi regex yang ditunjukkan di sini tidak.
AlexVhr

7

Gunakan lookahead:

Di Python 3.7, Anda dapat melakukan ini:

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

Dan itu menghasilkan:

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

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

atau

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

1
Filter ini sama sekali tidak diperlukan dan tidak membeli apa pun kepada Anda melalui pemisahan regex langsung dengan grup tangkapan: [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]memberi['The', 'Long', 'And', 'Winding', 'Road']
smci

1
@smci: Penggunaan filterini sama dengan pemahaman daftar dengan kondisi. Apakah Anda menentangnya?
Gabe

1
Saya tahu itu dapat diganti dengan pemahaman daftar dengan kondisi, karena saya baru saja memposting kode itu, lalu Anda menyalinnya. Berikut adalah tiga alasan mengapa pemahaman daftar lebih disukai: a) Idiom yang terbaca: pemahaman daftar adalah idiom yang lebih Pythonic dan membaca lebih jelas dari kiri ke kanan daripada filter(lambdaconditionfunc, ...)b) di Python 3, filter()mengembalikan sebuah iterator. Jadi mereka tidak akan setara sepenuhnya. c) Saya perkirakan filter()lebih lambat juga
smci

5

Saya pikir jawaban yang lebih baik mungkin adalah membagi string menjadi kata-kata yang tidak diakhiri dengan huruf kapital. Ini akan menangani kasus di mana string tidak dimulai dengan huruf kapital.

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

contoh:

>>> 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
Bisakah Anda menambahkan penjelasan mengapa ini adalah solusi yang baik untuk masalah tersebut.
Matas Vaitkevicius

Maafkan saya. Saya lupa langkah terakhir
pengguna3726655

Sepertinya ringkas, pythonic, dan cukup jelas, bagi saya.

2

Solusi alternatif (jika Anda tidak menyukai ekspresi reguler):

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

Lain tanpa regex dan kemampuan untuk menjaga huruf besar jika diinginkan

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

Ini dimungkinkan dengan more_itertools.split_beforealat tersebut.

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

Itu juga harus membagi kejadian tunggal, yaitu dari yang 'ABC'ingin saya dapatkan ['A', 'B', 'C'].

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

more_itertoolsadalah paket pihak ketiga dengan 60+ alat berguna termasuk implementasi untuk semua resep itertools asli , yang meniadakan implementasi manualnya.


1

Cara pythonic bisa jadi:

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

Berfungsi baik untuk Unicode, hindari re / re2.

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

0

Cara alternatif tanpa menggunakan regex atau enumerate:

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

Saya pikir ini lebih jelas dan sederhana tanpa merangkai terlalu banyak metode atau menggunakan pemahaman daftar panjang yang mungkin sulit untuk dibaca.


0

Cara alternatif menggunakan enumeratedanisupper()

Kode:

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

Keluaran:

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

0

Berbagi apa yang terlintas di benak saya saat membaca postingan. Beda dari postingan lainnya.

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

Ganti setiap huruf besar 'L' yang diberikan dengan spasi kosong ditambah huruf "L". Kita dapat melakukan ini dengan menggunakan pemahaman daftar atau kita dapat mendefinisikan fungsi untuk melakukannya sebagai berikut.

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

Jika Anda memilih menggunakan suatu fungsi, berikut caranya.

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

Dalam kasus contoh yang diberikan:

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

Tetapi sebagian besar waktu kita memisahkan kalimat menjadi huruf besar, biasanya kita ingin mempertahankan singkatan yang biasanya merupakan aliran huruf besar yang berkelanjutan. Kode di bawah ini akan membantu.

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

Terima kasih.


@MarkByers Saya tidak tahu mengapa seseorang memilih jawaban saya, tetapi saya ingin Anda melihatnya untuk saya. Saya sangat menghargai tanggapan Anda.
Samuel Nde
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.