แบ่งสตริงออกเป็นคำที่มีตัวคั่นขอบเขตคำหลายคำ


671

ฉันคิดว่าสิ่งที่ฉันต้องการทำเป็นงานที่ค่อนข้างธรรมดา แต่ฉันไม่พบข้อมูลอ้างอิงบนเว็บ ฉันมีข้อความที่มีเครื่องหมายวรรคตอนและฉันต้องการรายการคำศัพท์

"Hey, you - what are you doing here!?"

ควรจะเป็น

['hey', 'you', 'what', 'are', 'you', 'doing', 'here']

แต่ Python str.split()ใช้งานได้กับอาร์กิวเมนต์เดียวเท่านั้นดังนั้นฉันจึงมีคำทั้งหมดด้วยเครื่องหมายวรรคตอนหลังจากที่ฉันแยกด้วยช่องว่าง ความคิดใด ๆ



6
งูเหลือมstr.split()ยังทำงานได้โดยไม่มีข้อโต้แย้งเลย
Ivan Vinogradov

คำตอบ:


468

กรณีที่นิพจน์ทั่วไปมีความชอบธรรม:

import re
DATA = "Hey, you - what are you doing here!?"
print re.findall(r"[\w']+", DATA)
# Prints ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

2
ขอบคุณ ยังมีความสนใจ - ฉันจะใช้อัลกอริทึมที่ใช้ในโมดูลนี้ได้อย่างไร และทำไมถึงไม่ปรากฏในโมดูลสตริง
ooboo

29
การแสดงผลปกติสามารถทำได้ในตอนแรก แต่มีประสิทธิภาพมาก นิพจน์ทั่วไป '\ w +' หมายถึง "อักขระคำ (az เป็นต้น) ซ้ำหนึ่งครั้งขึ้นไป" มี HOWTO ในการแสดงผลปกติของ Python ที่นี่: amk.ca/python/howto/regex
RichieHindle

324
นี่ไม่ใช่คำตอบสำหรับคำถาม นี่คือคำตอบสำหรับคำถามอื่นที่เกิดขึ้นกับสถานการณ์นี้โดยเฉพาะ ราวกับว่ามีคนถามว่า "ฉันจะเลี้ยวซ้ายได้อย่างไร" และคำตอบที่ได้รับคะแนนสูงสุดคือ "เลี้ยวขวาสามครั้งถัดไป" มันใช้งานได้สำหรับทางแยกบางแห่ง แต่มันก็ไม่ได้ให้คำตอบที่ต้องการ กระแทกแดกดันคำตอบคือในเพียงไม่re findallคำตอบด้านล่างให้re.split()ดีกว่า
Jesse Dhillon

4
@JesseDhillon "ใช้สตริงย่อยทั้งหมดซึ่งประกอบด้วยลำดับของอักขระคำ" และ "แยกบนสตริงย่อยทั้งหมดที่ประกอบด้วยลำดับของอักขระที่ไม่ใช่คำ" นั้นแท้จริงแล้วเป็นวิธีที่แตกต่างกันในการแสดงการดำเนินการเดียวกัน ฉันไม่แน่ใจว่าทำไมคุณถึงเรียกคำตอบอย่างใดอย่างหนึ่งที่เหนือกว่า
Mark Amery

4
@TMWP: ผู้ apostophe หมายความว่าคำเหมือนdon'tจะถือว่าเป็นคำเดียวแทนที่จะเป็นแยกออกเป็นและdon t
RichieHindle

574

re.split ()

re.split (รูปแบบ, สตริง [, maxsplit = 0])

แยกสตริงด้วยการเกิดขึ้นของรูปแบบ หากมีการใช้วงเล็บในรูปแบบแล้วข้อความของกลุ่มทั้งหมดในรูปแบบจะถูกส่งกลับเป็นส่วนหนึ่งของรายการผลลัพธ์ ถ้า maxsplit ไม่ใช่ศูนย์จะเกิดการแยก maxsplit ส่วนใหญ่และส่วนที่เหลือของสตริงจะถูกส่งกลับเป็นองค์ประกอบสุดท้ายของรายการ (หมายเหตุความเข้ากันไม่ได้: ในรุ่น Python 1.5 ดั้งเดิม maxsplit จะถูกละเว้นซึ่งได้รับการแก้ไขในรุ่นที่ใหม่กว่า)

>>> re.split('\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split('(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split('\W+', 'Words, words, words.', 1)
['Words', 'words, words.']

13
โซลูชันนี้มีข้อได้เปรียบในการปรับให้แยกได้ง่ายโดยใช้เครื่องหมายขีดล่างด้วยเช่นกันสิ่งที่โซลูชัน findall ไม่ได้: print re.split ("\ W + | _", "การทดสอบ this_thing") 'อัตราผลตอบแทน:' 'การทดสอบ', 'this' , 'thing']
Emil Stenström

63
ตอนนี้ถ้าเดียวที่ฉันจะจำความแตกต่างระหว่าง\w, \W, และ\s \Sใครก็ตามที่คิดว่าการใช้อักษรตัวพิมพ์ใหญ่เป็นธงควรกลับความหมายของภาพนั้นด้วยการตีหัว
ArtOfWarfare

1
กรณีการใช้งานทั่วไปของการแยกสตริงกำลังลบรายการสตริงว่างออกจากผลลัพธ์สุดท้าย เป็นไปได้ไหมที่จะใช้วิธีนี้? re.split ('\ W +', 'abc') ให้ผลลัพธ์เป็น ['', 'a', 'b', 'c', '']
Scott Morken

3
@ArtOfWarfare เป็นเรื่องปกติที่จะใช้shiftกุญแจในการทำสิ่งที่ตรงกันข้าม ctrl+zเลิกทำกับctrl+shift+zการทำซ้ำ ดังนั้นshift wหรือจะเป็นตรงข้ามของW w
Frank Vel

1
คำตอบนี้ควรอยู่ที่ด้านบนสุด - เป็นคำตอบเดียวที่ตอบคำถามได้อย่างแม่นยำ
Kranach

381

อีกวิธีที่รวดเร็วในการทำเช่นนี้โดยไม่มี regexp คือการแทนที่อักขระก่อนดังนี้:

>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']

71
รวดเร็วและสกปรก แต่สมบูรณ์แบบสำหรับกรณีของฉัน (ตัวคั่นของฉันมีชุดเล็ก ๆ ที่รู้จัก)
Andy Baker

7
สมบูรณ์แบบสำหรับกรณีที่คุณไม่สามารถเข้าถึงไลบรารี RE เช่นไมโครคอนโทรลเลอร์ขนาดเล็กบางตัว :-)
tu-Reinstate Monica-dor duh

11
ฉันคิดว่านี่ชัดเจนกว่า RE เช่นกันดังนั้นมันจึงเป็นมิตร noob บางครั้งไม่ต้องการวิธีแก้ปัญหาทั่วไปสำหรับทุกสิ่ง
อดัมฮิวจ์

น่ากลัว ฉันมี. แยก () ในสถานการณ์อินพุตหลายรายการและจำเป็นต้องจับเมื่อผู้ใช้ฉันแยกอินพุตด้วยช่องว่างไม่ใช่เครื่องหมายจุลภาค ฉันกำลังจะยอมแพ้และแต่งใหม่ด้วยซ้ำ แต่โซลูชัน. แทนที่ () ของคุณกระแทกเล็บบนหัว ขอบคุณ
JayJay123

มันจะทำให้คุณได้รับคำตอบที่ผิดเมื่อคุณไม่ต้องการแยกช่องว่างและคุณต้องการแยกอักขระอื่น ๆ
Ahmed Amr

307

คำตอบมากมาย แต่ฉันไม่สามารถหาวิธีแก้ปัญหาที่มีประสิทธิภาพในสิ่งที่ชื่อของคำถามที่แท้จริงถาม (แยกตัวแยกที่เป็นไปได้หลาย - แทนคำตอบมากมายแยกกับสิ่งที่ไม่ใช่คำซึ่งแตกต่างกัน) ดังนั้นนี่คือคำตอบของคำถามในชื่อเรื่องซึ่งขึ้นอยู่กับreโมดูลมาตรฐานและประสิทธิภาพของ Python :

>>> import re  # Will be splitting on: , <space> - ! ? :
>>> filter(None, re.split("[, \-!?:]+", "Hey, you - what are you doing here!?"))
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

ที่อยู่:

  • […]แมตช์หนึ่งของตัวคั่นที่ระบุไว้ภายใน
  • \-ในการแสดงออกปกติอยู่ที่นี่เพื่อป้องกันการตีความที่พิเศษของการ-เป็นตัวบ่งชี้ช่วงตัวละคร (ในA-Z)
  • +ข้ามหนึ่งหรือมากกว่าตัวคั่น (มันอาจจะถูกมองข้ามขอบคุณfilter()แต่ไม่จำเป็นจะผลิตสตริงว่างระหว่างคั่นจับคู่) และ
  • filter(None, …) ลบสตริงว่างที่อาจสร้างขึ้นโดยตัวแยกนำหน้าและต่อท้าย (เนื่องจากสตริงว่างมีค่าบูลีนเท็จ)

นี้ re.split()"แยกด้วยตัวคั่นหลายตัว"อย่างแม่นยำตามที่ถามในชื่อคำถาม

วิธีการแก้ปัญหานี้นอกจากนี้ยังมีภูมิคุ้มกันต่อปัญหาที่เกิดขึ้นกับตัวละครที่ไม่ใช่ ASCII ในคำที่พบในการแก้ปัญหาอื่น ๆ (ดูความคิดเห็นแรกที่คำตอบของ ghostdog74 )

reโมดูลมีประสิทธิภาพมากขึ้น (ในความเร็วและกระชับ) กว่าการทำลูปหลามและการทดสอบ "ด้วยมือ"!


3
"ฉันไม่สามารถหาวิธีการแก้ปัญหาใด ๆ ที่ไม่ได้อย่างมีประสิทธิภาพสิ่งที่ชื่อของคำถามที่แท้จริงถามว่า" - คำตอบที่สองไม่ว่าโพสต์ 5 ปีที่ผ่านมา: stackoverflow.com/a/1059601/2642204
BartoszKP

17
คำตอบนี้จะไม่แยกที่ตัวคั่น (จากชุดของตัวคั่นหลายตัว): มันแยกออกจากสิ่งใดก็ตามที่ไม่ใช่ตัวอักษรและตัวเลขแทน ที่กล่าวว่าฉันยอมรับว่าความตั้งใจของผู้โพสต์ดั้งเดิมน่าจะเก็บไว้เฉพาะคำแทนที่จะเอาเครื่องหมายวรรคตอนออก
Eric O Lebigot

EOL: ฉันคิดว่าคำตอบนี้แบ่งออกเป็นหลายชุด หากคุณเพิ่มตัวเลขที่ไม่ใช่ตัวอักษรลงในสตริงที่ไม่ได้ระบุเช่นขีดล่างพวกเขาจะไม่แยกตามที่คาดไว้
GravityWell

@ GravityWell: ฉันไม่แน่ใจว่าฉันเข้าใจ: คุณสามารถยกตัวอย่างที่เป็นรูปธรรมได้หรือไม่?
Eric O Lebigot

3
@EOL: ฉันเพิ่งตระหนักว่าฉันสับสนกับความคิดเห็นของคุณ "คำตอบนี้ไม่ได้แยก ... " ฉันคิดว่า "นี่" หมายถึงคำตอบ replit ของคุณ แต่ตอนนี้ฉันตระหนักว่าคุณหมายถึงคำตอบของ Gimel ผมคิดว่าคำตอบนี้ (คำตอบที่ฉันแสดงความคิดเห็น) เป็นคำตอบที่ดีที่สุด :)
GravityWell

56

อีกวิธีหนึ่งโดยไม่ต้อง regex

import string
punc = string.punctuation
thestring = "Hey, you - what are you doing here!?"
s = list(thestring)
''.join([o for o in s if not o in punc]).split()

8
วิธีนี้ดีกว่าโซลูชันที่ยอมรับ จะทำงานร่วมกับไม่มีตัวอักษร ASCII "Hey, you - what are you doing here María!?"ลอง โซลูชันที่ยอมรับจะไม่ทำงานกับตัวอย่างก่อนหน้า
Christopher Ramírez

4
ฉันคิดว่ามีปัญหาเล็กน้อยที่นี่ ... รหัสของคุณจะต่อท้ายอักขระที่คั่นด้วยเครื่องหมายวรรคตอนและจะไม่แยก ... ถ้าฉันไม่ผิดบรรทัดสุดท้ายของคุณควรเป็น:''.join([o if not o in string.punctuation else ' ' for o in s]).split()
cedbeu

ไลบรารีนิพจน์ทั่วไปสามารถทำให้ยอมรับข้อตกลง Unicode สำหรับอักขระได้หากจำเป็น นอกจากนี้ยังมีปัญหาเดียวกันกับวิธีแก้ปัญหาที่ยอมรับซึ่งเคยมี: เนื่องจากตอนนี้แยกออกเป็นเครื่องหมายอะโพสโทรฟี คุณอาจต้องการo for o in s if (o in not string.punctuation or o == "'")แต่จากนั้นมันก็ซับซ้อนเกินไปสำหรับหนึ่งซับถ้าเราเพิ่มใน patch ของ cedbeu ด้วย
Daniel H

มีปัญหาอื่นที่นี่ แม้ว่าเราจะคำนึงถึงการเปลี่ยนแปลงของ @cedbeu แล้วรหัสนี้จะไม่ทำงานหากสตริงนั้นเป็นสิ่งที่ต้องการ"First Name,Last Name,Street Address,City,State,Zip Code"และเราต้องการแยกในคอมม่า,เท่านั้น ผลลัพธ์ที่ต้องการคือ: ['First Name', 'Last Name', 'Street Address', 'City', 'State', 'Zip Code']สิ่งที่เราได้รับแทน:['First', 'Name', 'Last', 'Name', 'Street', 'Address', 'City', 'State', 'Zip', 'Code']
Stefan van den Akker

4
วิธีการแก้ปัญหานี้ไม่มีประสิทธิภาพมาก: รายการแรกจะถูกแยกออกเป็นอักขระแต่ละตัวจากนั้นทั้งอักขระเครื่องหมายวรรคตอนทั้งหมดจะผ่านไปสำหรับแต่ละอักขระเดี่ยวในสตริงเดิมจากนั้นอักขระจะรวมกันแล้วแยกอีกครั้ง "การเคลื่อนไหว" ทั้งหมดนี้มีความซับซ้อนมากเช่นกันเมื่อเทียบกับโซลูชันที่ใช้นิพจน์ทั่วไป: แม้ว่าความเร็วจะไม่สำคัญในแอปพลิเคชันที่ระบุ แต่ก็ไม่จำเป็นต้องมีโซลูชันที่ซับซ้อน เนื่องจากreโมดูลเป็นมาตรฐานและให้ทั้งความชัดเจนและความเร็วฉันไม่เห็นสาเหตุที่ควรหลีกเลี่ยง
Eric O Lebigot

39

เคล็ดลับ: ใช้string.translateสำหรับการดำเนินการกับสตริงที่เร็วที่สุดที่ Python มี

หลักฐานบางอย่าง ...

ก่อนวิธีที่ช้า (ขออภัย pprzemek):

>>> import timeit
>>> S = 'Hey, you - what are you doing here!?'
>>> def my_split(s, seps):
...     res = [s]
...     for sep in seps:
...         s, res = res, []
...         for seq in s:
...             res += seq.split(sep)
...     return res
... 
>>> timeit.Timer('my_split(S, punctuation)', 'from __main__ import S,my_split; from string import punctuation').timeit()
54.65477919578552

ต่อไปเราใช้re.findall()(ตามที่กำหนดโดยคำตอบที่แนะนำ) เร็วขึ้นมาก:

>>> timeit.Timer('findall(r"\w+", S)', 'from __main__ import S; from re import findall').timeit()
4.194725036621094

ในที่สุดเราใช้translate:

>>> from string import translate,maketrans,punctuation 
>>> T = maketrans(punctuation, ' '*len(punctuation))
>>> timeit.Timer('translate(S, T).split()', 'from __main__ import S,T,translate').timeit()
1.2835021018981934

คำอธิบาย:

string.translate ถูกนำมาใช้ใน C และแตกต่างจากฟังก์ชั่นการจัดการสตริงใน Python string.translate ไม่ได้สร้างสตริงใหม่ ดังนั้นมันเร็วพอที่คุณจะได้รับการแทนที่สตริง

แม้ว่ามันจะค่อนข้างงุ่มง่ามเพราะมันต้องการโต๊ะแปลเพื่อทำเวทย์มนตร์นี้ คุณสามารถสร้างตารางการแปลได้ด้วยmaketrans()ฟังก์ชั่นอำนวยความสะดวก วัตถุประสงค์นี่คือการแปลอักขระที่ไม่ต้องการทั้งหมดเป็นช่องว่าง ตัวแทนแบบหนึ่งต่อหนึ่ง ไม่มีการสร้างข้อมูลใหม่อีกครั้ง ดังนั้นนี่คือรวดเร็ว !

split()ต่อไปเราจะใช้ดีเก่า split()โดยค่าเริ่มต้นจะทำงานกับอักขระช่องว่างทั้งหมดจัดกลุ่มไว้ด้วยกันเพื่อแยก ผลลัพธ์จะเป็นรายการคำที่คุณต้องการ และวิธีนี้เร็วกว่าเกือบ 4x re.findall()!


4
ฉันทำการทดสอบที่นี่และถ้าคุณต้องการใช้ยูนิโค้ดการใช้patt = re.compile(ur'\w+', re.UNICODE); patt.findall(S)เร็วกว่าการแปลเพราะคุณต้องเข้ารหัสสตริงก่อนที่จะใช้การแปลงและถอดรหัสแต่ละรายการในรายการหลังจากที่แยกกลับไปเป็นยูนิโค้ด
Rafael S. Calsaverini

คุณสามารถซับการใช้งานการแปลและทำให้แน่ใจว่า S ไม่ใช่หนึ่งในตัวแยกด้วย:s.translate(''.join([(chr(i) if chr(i) not in seps else seps[0]) for i in range(256)])).split(seps[0])
hobs

ไม่ได้ถ่ายเลย คุณกำลังเปรียบเทียบแอปเปิ้ลและส้ม ;) โซลูชันของฉันใน python 3 ยังใช้งานได้ P และได้รับการสนับสนุนสำหรับตัวคั่นหลายตัว :) ลองทำอย่างง่าย ๆ โดยไม่ต้องจัดสรรสตริงใหม่ :) แต่จริงของฉันนั้น จำกัด อยู่ที่การแยกวิเคราะห์บรรทัดคำสั่งไม่ใช่ตัวอย่างเช่นหนังสือ
pprzemek

คุณพูดว่า "ไม่ผลิตสตริงใหม่" ซึ่งหมายความว่ามันทำงานได้บนสตริงที่กำหนด? ฉันทดสอบตอนนี้ด้วย python 2.7 และไม่แก้ไขสตริงเดิมและส่งคืนใหม่
Prokop Hapala

26

ฉันมีปัญหาคล้ายกันและไม่ต้องการใช้โมดูล 'อีก'

def my_split(s, seps):
    res = [s]
    for sep in seps:
        s, res = res, []
        for seq in s:
            res += seq.split(sep)
    return res

print my_split('1111  2222 3333;4444,5555;6666', [' ', ';', ','])
['1111', '', '2222', '3333', '4444', '5555', '6666']

1
ฉันชอบสิ่งนี้. แค่ทราบลำดับของตัวแยกมีความสำคัญ ขออภัยถ้าเห็นได้ชัด
crizCraig

2
ทำไมไม่ใช้reโมดูลซึ่งเร็วกว่าและชัดเจนกว่า (ไม่ใช่นิพจน์ทั่วไปนั้นชัดเจนเป็นพิเศษ แต่เพราะสั้นและตรงมาก)
Eric O Lebigot

13

ก่อนอื่นฉันต้องการเห็นด้วยกับคนอื่น ๆ ว่า regex หรือstr.translate(...)โซลูชันพื้นฐานนั้นมีประสิทธิภาพมากที่สุด สำหรับกรณีการใช้งานของฉันประสิทธิภาพของฟังก์ชั่นนี้ไม่สำคัญดังนั้นฉันจึงต้องการเพิ่มแนวคิดที่ฉันพิจารณาด้วยเกณฑ์นั้น

เป้าหมายหลักของฉันคือการพูดคุยแนวคิดจากคำตอบอื่น ๆ ในโซลูชันที่สามารถใช้กับสตริงที่มีมากกว่าคำ regex (เช่นบัญชีดำส่วนย่อยที่ชัดเจนของอักขระเครื่องหมายวรรคตอนและตัวอักษรคำที่อนุญาตพิเศษ)

โปรดทราบว่าในวิธีการใดวิธีหนึ่งอาจพิจารณาใช้string.punctuationแทนรายการที่กำหนดด้วยตนเอง

ตัวเลือก 1 - re.sub

ฉันรู้สึกประหลาดใจที่จะเห็นไม่มีคำตอบเพื่อให้ห่างไกลใช้re.sub ( ... ) ฉันพบว่าวิธีนี้เป็นวิธีที่ง่ายและเป็นธรรมชาติสำหรับปัญหานี้

import re

my_str = "Hey, you - what are you doing here!?"

words = re.split(r'\s+', re.sub(r'[,\-!?]', ' ', my_str).strip())

ในโซลูชันนี้ฉันซ้อนการโทรre.sub(...)ภายในre.split(...)- แต่ถ้าประสิทธิภาพมีความสำคัญการรวบรวม regex ภายนอกอาจเป็นประโยชน์ - สำหรับกรณีการใช้งานของฉันความแตกต่างไม่สำคัญดังนั้นฉันชอบความเรียบง่ายและอ่านง่าย

ตัวเลือก 2 - str.replace

นี่เป็นอีกสองสามบรรทัด แต่มีประโยชน์ในการขยายได้โดยไม่ต้องตรวจสอบว่าคุณต้องหลบหนีอักขระบางตัวใน regex

my_str = "Hey, you - what are you doing here!?"

replacements = (',', '-', '!', '?')
for r in replacements:
    my_str = my_str.replace(r, ' ')

words = my_str.split()

มันจะเป็นการดีที่สามารถแมป str.replace กับสตริงแทน แต่ฉันไม่คิดว่ามันจะสามารถทำได้ด้วยสายที่ไม่เปลี่ยนรูปและในขณะที่การแมปกับรายชื่อตัวละครจะใช้งานได้ ฟังดูมากเกินไป (แก้ไข: ดูตัวเลือกถัดไปสำหรับตัวอย่างการทำงาน)

ตัวเลือก 3 - functools.reduce

(ใน Python 2 reduceมีอยู่ในเนมสเปซส่วนกลางโดยไม่ต้องนำเข้าจากฟังก์ชั่นเครื่องมือ)

import functools

my_str = "Hey, you - what are you doing here!?"

replacements = (',', '-', '!', '?')
my_str = functools.reduce(lambda s, sep: s.replace(sep, ' '), replacements, my_str)
words = my_str.split()

อืมวิธีการอีกวิธีหนึ่งก็คือการใช้str.translate- มันไม่ได้เป็น Unicode ที่มีความสามารถ แต่ส่วนใหญ่จะเร็วกว่าวิธีอื่นและอาจเป็นวิธีที่ดีในบางกรณี: replacements=',-!?'; import string; my_str = my_str.translate(string.maketrans(replacements, ' ' * len(replacements)))นอกจากนี้ยังจำเป็นต้องมีการแทนที่เป็นสตริงของอักขระไม่ใช่ tuple หรือ รายการ.
MarSoft

@ MarSoft ขอบคุณ! ฉันบอกว่าหนึ่งที่ด้านบนของคำตอบ แต่ตัดสินใจที่จะไม่เพิ่มเพราะคำตอบที่มีอยู่แล้วกล่าวถึงมันได้ดี
Taylor Edmiston

10
join = lambda x: sum(x,[])  # a.k.a. flatten1([[1],[2,3],[4]]) -> [1,2,3,4]
# ...alternatively...
join = lambda lists: [x for l in lists for x in l]

จากนั้นจะกลายเป็นสามซับ:

fragments = [text]
for token in tokens:
    fragments = join(f.split(token) for f in fragments)

คำอธิบาย

นี่คือสิ่งที่อยู่ใน Haskell เรียกว่า List monad แนวคิดเบื้องหลัง monad คือเมื่อ "ใน monad" คุณ "อยู่ใน monad" จนกว่าสิ่งที่จะนำคุณออก ตัวอย่างเช่นใน Haskell สมมติว่าคุณจับคู่range(n) -> [1,2,...,n]ฟังก์ชันpython กับรายการ หากผลลัพธ์นั้นเป็นรายการผลลัพธ์นั้นจะถูกผนวกเข้ากับรายการแบบแทนที่ดังนั้นคุณจะได้รับสิ่งที่ต้องการmap(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]หากผลที่ได้คือรายการมันจะผนวกในรายการในสถานที่เพื่อให้คุณจะได้รับสิ่งที่ต้องการสิ่งนี้เรียกว่า map-append (หรือ mappend หรืออาจเป็นแบบนั้น) แนวคิดที่นี่คือคุณได้รับการดำเนินการที่คุณกำลังใช้ (แยกโทเค็น) และเมื่อใดก็ตามที่คุณทำคุณจะเข้าร่วมผลลัพธ์ในรายการ

คุณสามารถสรุปสิ่งนี้เป็นฟังก์ชั่นและมีtokens=string.punctuationค่าเริ่มต้น

ข้อดีของวิธีนี้:

  • วิธีการนี้ (ซึ่งแตกต่างจากวิธีการที่ใช้วิธี regex แบบไร้เดียงสา) สามารถทำงานกับโทเค็นที่มีความยาวโดยพลการ
  • คุณไม่ได้ จำกัด เพียงโทเค็นเท่านั้น คุณอาจมีเหตุผลโดยพลการแทนโทเค็นแต่ละตัวตัวอย่างเช่นหนึ่งใน "โทเค็น" อาจเป็นฟังก์ชั่นที่แยกตามลักษณะของวงเล็บที่ซ้อนกัน

Neat Haskell solution แต่ IMO สามารถเขียนได้ชัดเจนยิ่งขึ้นโดยไม่ต้องผนวกกับ Python
Vlad the Impala

@ Goose: ประเด็นก็คือฟังก์ชั่น 2 บรรทัดmap_then_appendสามารถใช้ในการสร้างปัญหาแบบ 2 ซับได้รวมถึงปัญหาอื่น ๆ อีกมากมายที่เขียนได้ง่ายกว่ามาก โซลูชันอื่น ๆ ส่วนใหญ่ใช้reโมดูลนิพจน์ทั่วไปซึ่งไม่ใช่ python แต่ฉันไม่มีความสุขกับวิธีที่ฉันทำให้คำตอบของฉันดูเหมือนไม่เหมาะสมและบวมเมื่อมันกระชับจริง ๆ ... ฉันจะแก้ไขมัน ...
ninjagecko

สิ่งนี้ควรจะทำงานใน Python ตามที่เขียนหรือไม่ fragmentsผลลัพธ์ของฉันเป็นเพียงรายการของอักขระในสตริง (รวมถึงโทเค็น)
Rick สนับสนุนโมนิกา

@RickTeachey: มันเหมาะกับฉันทั้ง python2 และ python3
ninjagecko

hmmmm บางทีตัวอย่างอาจจะคลุมเครือเล็กน้อย ฉันได้พยายามรหัสในคำตอบทุกประเภทของ ways- ที่แตกต่างกันรวมทั้งมีfragments = ['the,string'], fragments = 'the,string'หรือfragments = list('the,string')และไม่มีพวกเขามีการผลิตการส่งออกที่เหมาะสม
Rick สนับสนุนโมนิก้า

5

ลองนี้:

import re

phrase = "Hey, you - what are you doing here!?"
matches = re.findall('\w+', phrase)
print matches

สิ่งนี้จะพิมพ์ ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']


4

ใช้แทนที่สองครั้ง:

a = '11223FROM33344INTO33222FROM3344'
a.replace('FROM', ',,,').replace('INTO', ',,,').split(',,,')

ผลลัพธ์ใน:

['11223', '33344', '33222', '3344']

4

ฉันชอบอีกครั้งแต่นี่เป็นวิธีแก้ปัญหาของฉันหากปราศจาก:

from itertools import groupby
sep = ' ,-!?'
s = "Hey, you - what are you doing here!?"
print [''.join(g) for k, g in groupby(s, sep.__contains__) if not k]

sep .__ contain__เป็นวิธีที่ใช้โดยโอเปอเรเตอร์ 'in' โดยทั่วไปมันเป็นเช่นเดียวกับ

lambda ch: ch in sep

แต่สะดวกกว่าที่นี่

groupbyรับค่าสตริงและฟังก์ชันของเรา มันแยกสตริงในกลุ่มโดยใช้ฟังก์ชั่นนั้น: เมื่อใดก็ตามที่มีการเปลี่ยนแปลงค่าของฟังก์ชั่น - กลุ่มใหม่จะถูกสร้างขึ้น ดังนั้นกันยายน .__ contains__คือสิ่งที่เราต้องการ

groupbyส่งคืนลำดับคู่โดยที่ pair [0] เป็นผลลัพธ์ของฟังก์ชันของเราและ pair [1] เป็นกลุ่ม การใช้'if not k'เราจะกรองกลุ่มที่มีตัวคั่น (เนื่องจากผลลัพธ์ของsep .__ contain__เป็น True บนตัวคั่น) ทีนี้นั่นคือทั้งหมด - ตอนนี้เรามีลำดับของกลุ่มที่แต่ละคำเป็นคำ (จริง ๆ แล้วกลุ่มคือ iterable ดังนั้นเราจึงใช้การเข้าร่วมเพื่อแปลงเป็นสตริง)

โซลูชันนี้ค่อนข้างทั่วไปเนื่องจากใช้ฟังก์ชันเพื่อแยกสตริง (คุณสามารถแยกตามเงื่อนไขที่คุณต้องการ) นอกจากนี้มันไม่ได้สร้างสตริง / รายการกลาง (คุณสามารถลบการเข้าร่วมและการแสดงออกจะขี้เกียจเนื่องจากแต่ละกลุ่มเป็นตัววนซ้ำ)


4

แทนที่จะใช้ฟังก์ชั่นโมดูลใหม่ re.split คุณสามารถบรรลุผลลัพธ์เดียวกันโดยใช้วิธี series.str.split ของแพนด้า

ก่อนอื่นให้สร้างชุดข้อมูลด้วยสตริงด้านบนจากนั้นใช้วิธีการกับชุดข้อมูล

thestring = pd.Series("Hey, you - what are you doing here!?") thestring.str.split(pat = ',|-')

พารามิเตอร์patใช้ตัวคั่นและส่งกลับสตริงการแยกเป็นอาร์เรย์ ที่นี่ตัวคั่นทั้งสองจะถูกส่งผ่านโดยใช้ | (หรือผู้ประกอบการ) ผลลัพธ์จะเป็นดังนี้:

[Hey, you , what are you doing here!?]


1
ไม่ใช่เรื่องของ verbose แต่เป็นความจริงที่ว่าการนำเข้าทั้งห้องสมุด (ซึ่งฉันชอบ BTW) เพื่อดำเนินงานอย่างง่ายหลังจากแปลงสตริงเป็นซีรีย์แพนด้า & quot; ไม่ค่อยเป็นมิตร & quot;
zar3bski

3

ฉันทำความคุ้นเคยกับ Python อีกครั้งและต้องการสิ่งเดียวกัน ทางออกของ findall อาจจะดีกว่า แต่ฉันคิดว่าสิ่งนี้:

tokens = [x.strip() for x in data.split(',')]

ฉลาดควรทำงานกับโครงสร้างไวยากรณ์ภาษาอังกฤษทั้งหมดที่ฉันสามารถนึกได้ยกเว้น em-dash ที่ไม่มีช่องว่างตัวอย่างเช่นนี้ (แก้ปัญหาได้)
ninjagecko

3

การใช้ maketrans และการแปลคุณสามารถทำได้อย่างง่ายดายและเป็นระเบียบ

import string
specials = ',.!?:;"()<>[]#$=-/'
trans = string.maketrans(specials, ' '*len(specials))
body = body.translate(trans)
words = body.strip().split()

คำตอบที่ดีเยี่ยมสำหรับPython> = 3.6
revliscano

3

ในหลาม 3, คุณสามารถใช้วิธีการจากPY4E - งูใหญ่สำหรับทุกคน

เราสามารถแก้ปัญหาทั้งสองนี้โดยใช้วิธีการสตริงlower, และpunctuation translateวิธีtranslateที่ละเอียดอ่อนที่สุดคือ นี่คือเอกสารสำหรับtranslate:

your_string.translate(your_string.maketrans(fromstr, tostr, deletestr))

แทนที่ตัวอักษรในfromstrด้วยตัวอักษรที่อยู่ในตำแหน่งเดียวกันในและลบตัวอักษรทั้งหมดที่อยู่ในtostr และสามารถเป็นสตริงที่ว่างเปล่าและพารามิเตอร์สามารถละเว้นdeletestrfromstrtostrdeletestr

คุณสามารถดู "เครื่องหมายวรรคตอน":

In [10]: import string

In [11]: string.punctuation
Out[11]: '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'  

สำหรับตัวอย่างของคุณ:

In [12]: your_str = "Hey, you - what are you doing here!?"

In [13]: line = your_str.translate(your_str.maketrans('', '', string.punctuation))

In [14]: line = line.lower()

In [15]: words = line.split()

In [16]: print(words)
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']

สำหรับข้อมูลเพิ่มเติมคุณสามารถดู:


2
วิธีการแปล () และ maketrans () น่าสนใจ แต่วิธีนี้ล้มเหลวในการ "แยกที่ตัวคั่น" (หรือช่องว่าง): ตัวอย่างเช่น "มีถ้ำขนาดใหญ่อยู่ใน" จะทำให้คำ "cavein" ไม่ถูกต้องแทน จาก "ถ้ำ" ที่คาดหวังและ "ใน" ... ดังนั้นสิ่งนี้ไม่ได้ทำในสิ่งที่คำถามต้องการ
Eric O Lebigot

เช่นเดียวกับสิ่งที่ @EricLebigot แสดงความคิดเห็น วิธีการข้างต้นไม่ได้ทำในสิ่งที่คำถามถามมาเป็นอย่างดี
Jeremy Anifacc

2

อีกวิธีหนึ่งในการบรรลุเป้าหมายนี้คือการใช้ชุดเครื่องมือภาษาธรรมชาติ ( nltk )

import nltk
data= "Hey, you - what are you doing here!?"
word_tokens = nltk.tokenize.regexp_tokenize(data, r'\w+')
print word_tokens

ภาพพิมพ์นี้: ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

อุปสรรคที่ใหญ่ที่สุดของวิธีนี้คือการที่คุณต้องติดตั้งแพคเกจ nltk

ประโยชน์คือคุณสามารถทำสิ่งที่สนุกมากมายกับแพคเกจ nltk ที่เหลือเมื่อคุณได้รับโทเค็นของคุณ


1

ก่อนอื่นฉันไม่คิดว่าความตั้งใจของคุณคือการใช้เครื่องหมายวรรคตอนเป็นตัวคั่นในฟังก์ชันแบบแยก คำอธิบายของคุณแนะนำว่าคุณต้องการกำจัดเครื่องหมายวรรคตอนจากสตริงผลลัพธ์

ฉันเจอสิ่งนี้บ่อยมากและวิธีแก้ปัญหาตามปกติของฉันไม่ต้องการอีก

ฟังก์ชั่นแลมบ์ดาหนึ่งซับพร้อมรายการความเข้าใจ:

(ต้องการimport string):

split_without_punc = lambda text : [word.strip(string.punctuation) for word in 
    text.split() if word.strip(string.punctuation) != '']

# Call function
split_without_punc("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']


ฟังก์ชั่น (แบบดั้งเดิม)

ในฐานะที่เป็นฟังก์ชั่นแบบดั้งเดิมนี่เป็นเพียงสองบรรทัดที่มี list comprehension (เพิ่มเติมจากimport string):

def split_without_punctuation2(text):

    # Split by whitespace
    words = text.split()

    # Strip punctuation from each word
    return [word.strip(ignore) for word in words if word.strip(ignore) != '']

split_without_punctuation2("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

มันจะปล่อยให้คำย่อและยัติภังค์ยังคงเหมือนเดิม คุณสามารถใช้text.replace("-", " ")เพื่อเปลี่ยนยัติภังค์เป็นช่องว่างก่อนที่จะแยก

ฟังก์ชั่นทั่วไปที่ไม่มีแลมบ์ดาหรือรายการความเข้าใจ

สำหรับวิธีแก้ปัญหาทั่วไปเพิ่มเติม (ซึ่งคุณสามารถระบุอักขระที่ต้องการกำจัด) และหากไม่มีความเข้าใจในรายการคุณจะได้รับ:

def split_without(text: str, ignore: str) -> list:

    # Split by whitespace
    split_string = text.split()

    # Strip any characters in the ignore string, and ignore empty strings
    words = []
    for word in split_string:
        word = word.strip(ignore)
        if word != '':
            words.append(word)

    return words

# Situation-specific call to general function
import string
final_text = split_without("Hey, you - what are you doing?!", string.punctuation)
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

แน่นอนว่าคุณสามารถใช้ฟังก์ชั่นแลมบ์ดากับสตริงอักขระที่ระบุได้เช่นกัน


1

ก่อนอื่นให้ใช้ re.compile () ก่อนทำการดำเนินการ RegEx ใด ๆ ในการวนซ้ำเนื่องจากมันทำงานได้เร็วกว่าการดำเนินการปกติ

ดังนั้นสำหรับปัญหาของคุณก่อนรวบรวมรูปแบบแล้วดำเนินการกับมัน

import re
DATA = "Hey, you - what are you doing here!?"
reg_tok = re.compile("[\w']+")
print reg_tok.findall(DATA)

1

นี่คือคำตอบพร้อมคำอธิบายบางอย่าง

st = "Hey, you - what are you doing here!?"

# replace all the non alpha-numeric with space and then join.
new_string = ''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])
# output of new_string
'Hey  you  what are you doing here  '

# str.split() will remove all the empty string if separator is not provided
new_list = new_string.split()

# output of new_list
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

# we can join it to get a complete string without any non alpha-numeric character
' '.join(new_list)
# output
'Hey you what are you doing'

หรือในหนึ่งบรรทัดเราสามารถทำสิ่งนี้:

(''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])).split()

# output
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

คำตอบที่อัปเดต


1

สร้างฟังก์ชั่นที่รับอินพุตสองสาย (สตริงต้นทางที่จะแยกและสตริงตัวแยกรายการของตัวคั่น) และส่งออกรายการคำแยก:

def split_string(source, splitlist):
    output = []  # output list of cleaned words
    atsplit = True
    for char in source:
        if char in splitlist:
            atsplit = True
        else:
            if atsplit:
                output.append(char)  # append new word after split
                atsplit = False
            else: 
                output[-1] = output[-1] + char  # continue copying characters until next split
    return output

1

ฉันชอบวิธีแก้ปัญหาของ pprzemek เพราะไม่คิดว่าตัวคั่นเป็นตัวอักษรเดี่ยวและไม่พยายามใช้ประโยชน์จาก regex (ซึ่งจะไม่ทำงานได้ดีถ้าจำนวนตัวคั่นยาวเกินจริง)

นี่เป็นเวอร์ชันที่อ่านได้มากกว่าของโซลูชันด้านบนเพื่อความชัดเจน:

def split_string_on_multiple_separators(input_string, separators):
    buffer = [input_string]
    for sep in separators:
        strings = buffer
        buffer = []  # reset the buffer
        for s in strings:
            buffer = buffer + s.split(sep)

    return buffer

0

มีปัญหาเช่นเดียวกับ @ooboo และค้นหาหัวข้อนี้ @ ghostdog74 เป็นแรงบันดาลใจให้ฉันบางทีบางคนอาจพบว่าวิธีแก้ปัญหาของฉันมีประโยชน์

str1='adj:sg:nom:m1.m2.m3:pos'
splitat=':.'
''.join([ s if s not in splitat else ' ' for s in str1]).split()

ป้อนข้อมูลบางอย่างในที่ว่างและแยกโดยใช้อักขระเดียวกันหากคุณไม่ต้องการแยกที่เว้นวรรค


ถ้าฉันต้องแยกคำโดยใช้คำว่า?
Harsha Biyani

0

นี่คือการแยกของฉันที่มีผู้กระทำความผิดหลายคน:

def msplit( str, delims ):
  w = ''
  for z in str:
    if z not in delims:
        w += z
    else:
        if len(w) > 0 :
            yield w
        w = ''
  if len(w) > 0 :
    yield w

0

ฉันคิดว่าต่อไปนี้เป็นคำตอบที่ดีที่สุดเพื่อตอบสนองความต้องการของคุณ:

\W+ อาจเหมาะสำหรับกรณีนี้ แต่อาจไม่เหมาะสำหรับกรณีอื่น

filter(None, re.compile('[ |,|\-|!|?]').split( "Hey, you - what are you doing here!?")

ฉันเห็นด้วย\wและ\Wวิธีแก้ปัญหาไม่ใช่คำตอบสำหรับ (ชื่อเรื่อง) คำถาม โปรดทราบว่าในคำตอบของคุณ|ควรถูกลบออก (คุณกำลังคิดถึงexpr0|expr1แทนที่จะเป็น[char0 char1…]) นอกจากนี้ไม่จำเป็นต้องcompile()มีการแสดงออกปกติ
Eric O Lebigot

0

นี่ฉันใช้มัน ....

def split_string(source,splitlist):
    splits = frozenset(splitlist)
    l = []
    s1 = ""
    for c in source:
        if c in splits:
            if s1:
                l.append(s1)
                s1 = ""
        else:
            print s1
            s1 = s1 + c
    if s1:
        l.append(s1)
    return l

>>>out = split_string("First Name,Last Name,Street Address,City,State,Zip Code",",")
>>>print out
>>>['First Name', 'Last Name', 'Street Address', 'City', 'State', 'Zip Code']

0

ฉันชอบreplace()วิธีที่ดีที่สุด โพรซีเดอร์ต่อไปนี้เปลี่ยนตัวคั่นทั้งหมดที่กำหนดในสตริงsplitlistเป็นตัวคั่นแรกในsplitlistจากนั้นแยกข้อความบนตัวคั่นหนึ่งตัวนั้น นอกจากนี้ยังsplitlistคำนึงถึงว่าเป็นสตริงว่างหรือไม่ มันจะส่งคืนรายการคำโดยไม่มีสตริงว่างอยู่

def split_string(text, splitlist):
    for sep in splitlist:
        text = text.replace(sep, splitlist[0])
    return filter(None, text.split(splitlist[0])) if splitlist else [text]

0
def get_words(s):
    l = []
    w = ''
    for c in s.lower():
        if c in '-!?,. ':
            if w != '': 
                l.append(w)
            w = ''
        else:
            w = w + c
    if w != '': 
        l.append(w)
    return l

นี่คือการใช้งาน:

>>> s = "Hey, you - what are you doing here!?"
>>> print get_words(s)
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']

0

หากคุณต้องการการดำเนินการย้อนกลับ (รักษาตัวคั่น) คุณสามารถใช้ฟังก์ชั่นนี้:

def tokenizeSentence_Reversible(sentence):
    setOfDelimiters = ['.', ' ', ',', '*', ';', '!']
    listOfTokens = [sentence]

    for delimiter in setOfDelimiters:
        newListOfTokens = []
        for ind, token in enumerate(listOfTokens):
            ll = [([delimiter, w] if ind > 0 else [w]) for ind, w in enumerate(token.split(delimiter))]
            listOfTokens = [item for sublist in ll for item in sublist] # flattens.
            listOfTokens = filter(None, listOfTokens) # Removes empty tokens: ''
            newListOfTokens.extend(listOfTokens)

        listOfTokens = newListOfTokens

    return listOfTokens

0

ฉันเพิ่งต้องการทำสิ่งนี้ แต่ต้องการฟังก์ชั่นที่ค่อนข้างตรงกับstr.splitฟังก์ชั่นไลบรารีมาตรฐานฟังก์ชั่นนี้จะทำงานเหมือนกับไลบรารีมาตรฐานเมื่อถูกเรียกด้วยอาร์กิวเมนต์ 0 หรือ 1

def split_many(string, *separators):
    if len(separators) == 0:
        return string.split()
    if len(separators) > 1:
        table = {
            ord(separator): ord(separator[0])
            for separator in separators
        }
        string = string.translate(table)
    return string.split(separators[0])

หมายเหตุ : ฟังก์ชั่นนี้จะมีประโยชน์ก็ต่อเมื่อตัวแยกของคุณประกอบด้วยอักขระเดียว (เช่นเดียวกับกรณีของฉัน)

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