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

Federico A. Ramponi
sumber

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']
Mark Byers
sumber
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.

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

John La Rooy
sumber
+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
pwdyson.dll
sumber
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']
Endlisnis
sumber
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]
Gabe
sumber
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']
lihai
sumber
4
src = 'TheLongAndWindingRoad'
glue = ' '

result = ''.join(glue + x if x.isupper() else x for x in src).strip(glue).split(glue)
pengguna3726655
sumber
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
ChristopheD
sumber
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']
Totoro
sumber
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.

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

Pai 'Oh' Pah
sumber
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']
The6thSense
sumber
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
Do L.
sumber
-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.

Samuel Nde
sumber
@MarkByers Saya tidak tahu mengapa seseorang memilih jawaban saya, tetapi saya ingin Anda melihatnya untuk saya. Saya sangat menghargai tanggapan Anda.
Samuel Nde