แยกสตริงที่ตัวอักษรพิมพ์ใหญ่


101

เป็นสิ่งที่pythonicวิธีการแยกสตริงก่อนที่จะเกิดขึ้นของชุดที่กำหนดของตัวละครหรือไม่?

ตัวอย่างเช่นผมต้องการแยก 'TheLongAndWindingRoad' ที่เกิดขึ้นของอักษรตัวพิมพ์ใหญ่ (อาจจะยกเว้นแรก) ใด ๆ ['The', 'Long', 'And', 'Winding', 'Road']และได้รับ

แก้ไข: นอกจากนี้ยังควรแยกที่เกิดขึ้นเพียงครั้งเดียวคือจากฉันต้องการที่จะได้รับ'ABC' ['A', 'B', 'C']

คำตอบ:


143

น่าเสียดายที่ไม่สามารถแบ่งการจับคู่ความกว้างศูนย์ใน 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']
Marc Schulder

15
@MarcSchulder: ถ้าคุณต้องการกรณีนั้นให้ใช้'[a-zA-Z][^A-Z]*'เป็น regex
knub

เป็นไปได้ที่จะทำเช่นเดียวกันโดยไม่ต้องใช้ความผิดพลาด?
Laurent Cesaro

4
เพื่อแยกคำตัวพิมพ์เล็กอูฐprint(re.findall('^[a-z]+|[A-Z][^A-Z]*', 'theLongAndWindingRoad'))
hard_working_ant

35

นี่คือโซลูชัน regex ทางเลือก ปัญหานี้สามารถเปลี่ยนเป็น "ฉันจะใส่ช่องว่างก่อนตัวอักษรตัวพิมพ์ใหญ่แต่ละตัวก่อนทำการแยกได้อย่างไร":

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

สิ่งนี้มีข้อดีในการรักษาอักขระที่ไม่ใช่ช่องว่างทั้งหมดซึ่งโซลูชันอื่น ๆ ส่วนใหญ่ไม่มี


คุณช่วยอธิบายได้ไหมว่าทำไมช่องว่างก่อน \ 1 จึงทำงานได้ เป็นเพราะวิธีการแยกหรืออะไรที่เกี่ยวข้องกับ regex?
Lax_Sam

ค่าเริ่มต้นตัวคั่นแยกเป็นสตริงช่องว่างใด ๆ
CIsForCookies

@Lax_Sam เปลี่ยนตัว regex เพียงแค่เพิ่มพื้นที่ก่อนที่ตัวอักษรใด ๆ และแยก () หยิบมันขึ้นมา
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']

หากคุณต้องการ"It'sATest"แยกเพื่อ["It's", 'A', 'Test']เปลี่ยน rexeg เป็น"[A-Z][a-z']*"


+1: สำหรับคนแรกที่ทำให้ ABC ทำงานได้ ฉันยังอัปเดตคำตอบของฉันตอนนี้
Mark Byers

>>> re.findall ('[AZ] [az] *', "It's about 70% of the Economy") -----> ['It', 'Economy']
ChristopheD

@ChristopheD. OP ไม่ได้บอกว่าควรปฏิบัติกับอักขระที่ไม่ใช่อัลฟาอย่างไร
John La Rooy

1
จริง แต่วิธีการ regex ในปัจจุบันนี้ยังdropsรวมถึงคำปกติทั้งหมด (เพียงอัลฟาธรรมดา) ที่ไม่ได้ขึ้นต้นด้วยตัวอักษรตัวพิมพ์ใหญ่ ฉันสงสัยว่านั่นเป็นความตั้งใจของ OP
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
ดี - ใช้ได้กับอักขระที่ไม่ใช่ละตินด้วย โซลูชัน regex ที่แสดงที่นี่ไม่
AlexVhr


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
ตัวกรองไม่จำเป็นโดยสิ้นเชิงและไม่ต้องซื้ออะไรเลยจากการแยก regex โดยตรงกับกลุ่มการจับภาพ: [s for s in re.compile(r"([A-Z][^A-Z]*)").split( "TheLongAndWindingRoad") if s]ให้['The', 'Long', 'And', 'Winding', 'Road']
smci

1
@smci: การใช้งานfilterนี้เหมือนกับการเข้าใจรายการที่มีเงื่อนไข คุณมีอะไรต่อต้านหรือไม่?
Gabe

1
ฉันรู้ว่ามันสามารถแทนที่ด้วยความเข้าใจรายการด้วยเงื่อนไขได้เพราะฉันเพิ่งโพสต์รหัสนั้นจากนั้นคุณก็คัดลอก ต่อไปนี้เป็นเหตุผลสามประการที่ควรใช้ความเข้าใจในรายการ: ก) สำนวนที่ถูกต้อง:ความเข้าใจในรายการเป็นสำนวน Pythonic ที่มากกว่าและอ่านจากซ้ายไปขวาที่ชัดเจนกว่าfilter(lambdaconditionfunc, ...)b) ใน Python 3 filter()จะส่งคืนตัวทำซ้ำ ดังนั้นพวกเขาจะไม่เทียบเท่าทั้งหมด c) ฉันคาดว่าfilter()จะช้าลงเช่นกัน
smci

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
โปรดเพิ่มคำอธิบายว่าเหตุใดจึงเป็นวิธีแก้ปัญหาที่ดี
Matas Vaitkevicius

ฉันขอโทษ. ฉันลืมขั้นตอนสุดท้าย
user3726655

ดูเหมือนจะกระชับ, ไพ ธ อนิกและอธิบายตัวเองสำหรับฉัน

2

ทางเลือกอื่น (หากคุณไม่ชอบ regexes ที่โจ่งแจ้ง):

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

อีกอันที่ไม่มี regex และความสามารถในการรักษาตัวพิมพ์ใหญ่ที่อยู่ติดกันหากต้องการ

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

วิธี Pythonic อาจเป็น:

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

อีกทางเลือกหนึ่งโดยไม่ต้องใช้ regex หรือระบุ:

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" เราสามารถทำได้โดยใช้ list comp understandion หรือเราสามารถกำหนด function ให้ทำได้ดังนี้

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 ฉันไม่รู้ว่าทำไมมีคนโหวตคำตอบของฉัน แต่ฉันอยากให้คุณดูให้ฉัน ฉันขอขอบคุณสำหรับความคิดเห็นของคุณ
Samuel Nde
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.