Podziel ciągi na słowa z wieloma ogranicznikami granic słów

671

Myślę, że to, co chcę zrobić, jest dość powszechnym zadaniem, ale nie znalazłem żadnych odnośników w Internecie. Mam tekst ze znakami interpunkcyjnymi i chcę listę słów.

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

Powinien być

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

Ale Python str.split()działa tylko z jednym argumentem, więc mam wszystkie słowa z interpunkcją po podzieleniu spacją. Jakieś pomysły?

ooboo
źródło
6
Python str.split()działa również bez żadnych argumentów
Ivan Vinogradov

Odpowiedzi:

468

Przypadek, w którym wyrażenia regularne są uzasadnione:

import re
DATA = "Hey, you - what are you doing here!?"
print re.findall(r"[\w']+", DATA)
# Prints ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']
RichieHindle
źródło
2
Dzięki. Nadal jestem zainteresowany - jak mogę wdrożyć algorytm zastosowany w tym module? I dlaczego nie pojawia się w module ciągów?
ooboo
29
Wyrażenia regularne mogą początkowo zniechęcać, ale są bardzo potężne. Wyrażenie regularne „\ w +” oznacza „znak słowa (az itd.) Powtórzony jeden lub więcej razy”. Jest tutaj HOWTO na temat wyrażeń regularnych Pythona: amk.ca/python/howto/regex
RichieHindle
324
To nie jest odpowiedź na pytanie. To jest odpowiedź na inne pytanie, które działa w tej konkretnej sytuacji. To tak, jakby ktoś zapytał „jak skręcić w lewo”, a najczęściej głosowaną odpowiedzią było „skręć w następne trzy skręty w prawo”. Działa na niektórych skrzyżowaniach, ale nie daje potrzebnej odpowiedzi. Jak na ironię, odpowiedź brzmi: po reprostu nie findall. Poniższa odpowiedź re.split()jest lepsza.
Jesse Dhillon
4
@JesseDhillon „weź wszystkie podłańcuchy składające się z sekwencji znaków słownych” i „podziel na wszystkie podłańcuchy składające się z sekwencji znaków niebędących słowami” to dosłownie różne sposoby wyrażania tej samej operacji; Nie jestem pewien, dlaczego nazwałbyś którąkolwiek z tych odpowiedzi przełożonymi.
Mark Amery
4
@TMWP: Apostrof oznacza, że ​​słowo podobne don'tjest traktowane jako pojedyncze słowo, a nie w podziale na doni t.
RichieHindle
574

re.split ()

re.split (wzór, ciąg [, maxsplit = 0])

Podziel ciąg według wystąpień wzoru. Jeśli we wzorcu używane są nawiasy przechwytujące, tekst wszystkich grup we wzorcu jest również zwracany jako część wynikowej listy. Jeśli parametr maxsplit jest niezerowy, występują najwyżej podziały maxsplit, a pozostała część łańcucha jest zwracana jako ostatni element listy. (Uwaga dotycząca niezgodności: w oryginalnej wersji Python 1.5 maxsplit został zignorowany. Zostało to naprawione w późniejszych wersjach.)

>>> 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.']
gimel
źródło
13
Zaletą tego rozwiązania jest to, że można je łatwo dostosować do podziału na podkreślenia, czego nie rozwiązuje wszystkie znalezione rozwiązanie: drukuj re.split („\ W + | _”, „Testowanie tego_nazwa”) ”daje: [„ Testowanie ”,„ to ” , „rzecz”]
Emil Stenström
63
Teraz, jeśli tylko mogę zapamiętać różnicę pomiędzy \w, \W, \s, i \S. Ten, kto myślał, że wielkie litery powinny odwracać ich znaczenie, musi zostać postrzelony w głowę.
ArtOfWarfare
1
Częstym przypadkiem podziału ciągów jest usuwanie pustych pozycji ciągu z wyniku końcowego. Czy można to zrobić za pomocą tej metody? re.split ('\ W +', 'abc') powoduje, że ['', 'a', 'b', 'c', '']
Scott Morken
3
@ArtOfWarfare Często używa się shiftklucza, aby zrobić coś przeciwnego. ctrl+zcofnij vs. ctrl+shift+zprzywróć. Tak więc shift w, lub Wbyłoby odwrotnie w.
Frank Vel,
1
Ta odpowiedź powinna być na górze - jako jedyna precyzyjnie odpowiada na pytanie.
Kranach
381

Innym szybkim sposobem na zrobienie tego bez wyrażenia regularnego jest zastąpienie znaków, jak poniżej:

>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']
Louis LC
źródło
71
Szybki i brudny, ale idealny do mojej skrzynki (moje separatory były małym, znanym zestawem)
Andy Baker
7
Idealny w przypadku, gdy nie masz dostępu do biblioteki RE, na przykład niektórych małych mikrokontrolerów. :-)
tu-Reinstate Monica-dor duh
11
Myślę, że jest to również bardziej wyraźne niż RE, więc jest raczej przyjazne dla nooba. Czasami nie potrzebuje ogólnego rozwiązania wszystkiego
Adam Hughes
Niesamowite. Miałem .split () w sytuacji wielokrotnego wprowadzania danych i potrzebowałem złapać, gdy użytkownik, ja, oddzielił dane wejściowe spacją, a nie przecinkiem. Już miałem się poddać i przerobić z re, ale twoje rozwiązanie .replace () uderzyło w sedno. Dzięki.
JayJay123,
dostaniesz złą odpowiedź, jeśli nie chcesz dzielić na spacje i chcesz dzielić na inne postacie.
Ahmed Amr
307

Tak wielu odpowiedzi, ale nie mogę znaleźć żadnego rozwiązania, które by efektywnie spełniało oczekiwania dosłownie tytułu pytań (podział na wiele możliwych separatorów - zamiast tego wiele odpowiedzi dzieli się na wszystko, co nie jest słowem, co jest inne). Oto odpowiedź na pytanie zawarte w tytule, które opiera się na standardowym i wydajnym remodule Pythona :

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

gdzie:

  • z […]wyników jednym z podanych w separatorach,
  • \-w wyrażeniu regularnym jest tutaj, aby zapobiec szczególną interpretację -jako wskaźnik zakresu znaków (jak w A-Z)
  • +pomija jeden lub więcej ograniczników (może to być pominięte dzięki filter(), ale to niepotrzebnie produkować puste struny między dopasowanych separatorów), oraz
  • filter(None, …) usuwa puste ciągi, które mogą zostać utworzone przez separatory wiodące i końcowe (ponieważ puste ciągi mają fałszywą wartość boolowską).

To re.split()właśnie „dzieli z wieloma separatorami”, co postulowano w tytule pytanie.

To rozwiązanie jest ponadto odporne na problemy ze znakami spoza ASCII w słowach znalezionych w niektórych innych rozwiązaniach (patrz pierwszy komentarz do odpowiedzi ghostdog74 ).

reModuł jest znacznie bardziej efektywny (w szybkości i zwięzłości) niż robić pętle Python i testy „ręcznie”!

Eric O Lebigot
źródło
3
„Nie mogę znaleźć żadnego rozwiązania, które działałoby tak skutecznie, jak brzmi dosłownie tytuł pytania” - robi to druga odpowiedź, zamieszczona 5 lat temu: stackoverflow.com/a/1059601/2642204 .
BartoszKP,
17
Ta odpowiedź nie dzieli się na ograniczniki (z zestawu wielu ograniczników): zamiast tego dzieli na wszystko, co nie jest alfanumeryczne. To powiedziawszy, zgadzam się, że zamiarem oryginalnego plakatu jest prawdopodobnie zachowanie tylko słów, zamiast usuwania niektórych znaków interpunkcyjnych.
Eric O Lebigot,
EOL: Myślę, że ta odpowiedź dzieli się na zbiór wielu delimetrów. Jeśli dodasz niealfanumeryczne ciągi, które nie są określone, takie jak podkreślenie, nie zostaną one podzielone zgodnie z oczekiwaniami.
GravityWell,
@GravityWell: Nie jestem pewien, czy rozumiem: czy możesz podać konkretny przykład?
Eric O Lebigot,
3
@EOL: Właśnie zdałem sobie sprawę, że jestem zdezorientowany przez twój komentarz „Ta odpowiedź się nie dzieli…” „Myślałem, że” to odnosi się do twojej odpowiedzi od nowa, ale teraz zdaję sobie sprawę, że miałeś na myśli odpowiedź gimela. Myślę, że ta odpowiedź (odpowiedź, do której komentuję) jest najlepszą odpowiedzią :)
GravityWell,
56

Innym sposobem, bez wyrażenia regularnego

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()
ghostdog74
źródło
8
To rozwiązanie jest w rzeczywistości lepsze niż przyjęte. Działa bez znaków ASCII, spróbuj "Hey, you - what are you doing here María!?". Zaakceptowane rozwiązanie nie będzie działać z poprzednim przykładem.
Christopher Ramírez,
4
Myślę, że jest tu mały problem ... Twój kod będzie dołączał znaki oddzielone interpunkcją, a tym samym nie podzieli ich ... Jeśli się nie mylę, twoja ostatnia linia powinna brzmieć:''.join([o if not o in string.punctuation else ' ' for o in s]).split()
cedbeu
W razie potrzeby można utworzyć bibliotekę wyrażeń regularnych, aby akceptowała konwencje Unicode dla znaków. Dodatkowo ma to ten sam problem, co zwykłe rozwiązanie: jak obecnie, dzieli się na apostrofy. Możesz chcieć o for o in s if (o in not string.punctuation or o == "'"), ale wtedy staje się to zbyt skomplikowane dla jednej linijki, jeśli dodamy również łatkę cedbeu.
Daniel H
Jest tutaj inny problem. Nawet jeśli weźmiemy pod uwagę zmiany @cedbeu, ten kod nie działa, jeśli ciąg znaków jest podobny "First Name,Last Name,Street Address,City,State,Zip Code"i chcemy podzielić tylko przecinek ,. Pożądane wyjście byłoby: ['First Name', 'Last Name', 'Street Address', 'City', 'State', 'Zip Code']Co zamiast tego otrzymujemy:['First', 'Name', 'Last', 'Name', 'Street', 'Address', 'City', 'State', 'Zip', 'Code']
Stefan van den Akker
4
To rozwiązanie jest wyjątkowo nieefektywne: najpierw lista jest dekonstruowana na pojedyncze znaki, następnie cały zestaw znaków interpunkcyjnych przechodzi przez poszczególne znaki w oryginalnym ciągu, następnie znaki są składane z powrotem, a następnie dzielone ponownie. Cały ten „ruch” jest również bardzo skomplikowany w porównaniu do rozwiązania opartego na wyrażeniach regularnych: nawet jeśli prędkość nie ma znaczenia w danej aplikacji, nie ma potrzeby skomplikowanego rozwiązania. Ponieważ remoduł jest standardowy i zapewnia zarówno czytelność, jak i szybkość, nie rozumiem, dlaczego należy go unikać.
Eric O Lebigot,
39

Porada: Użyj string.translatedo najszybszych operacji na łańcuchach, które ma Python.

Jakiś dowód ...

Po pierwsze, droga wolna (przepraszam 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

Następnie używamy re.findall()(zgodnie z sugerowaną odpowiedzią). O wiele szybciej:

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

Wreszcie używamy 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

Wyjaśnienie:

string.translatejest zaimplementowany w C i w przeciwieństwie do wielu funkcji manipulacji ciągiem w Pythonie, string.translate nie robi tego tworzy nowego ciągu. Jest więc tak szybko, jak to możliwe, aby zastąpić ciąg znaków.

Jest to jednak trochę niezręczne, ponieważ do wykonania tej magii potrzebuje tabeli tłumaczeń. Możesz utworzyć tabelę tłumaczeń z maketrans()funkcją wygody. Celem jest przetłumaczenie wszystkich niechcianych postaci na spacje. Zastępca jeden do jednego. Ponownie nie są generowane żadne nowe dane. To jest szybkie !

Następnie używamy starego dobrego split(). split()domyślnie będzie działać na wszystkich znakach spacji, grupując je razem dla podziału. Rezultatem będzie lista słów, które chcesz. To podejście jest prawie 4x szybsze niż re.findall()!

Dave
źródło
4
Zrobiłem tutaj test, a jeśli chcesz użyć Unicode, użycie patt = re.compile(ur'\w+', re.UNICODE); patt.findall(S)jest szybsze niż tłumaczenie, ponieważ musisz zakodować ciąg przed zastosowaniem transformacji i zdekodować każdy element na liście po podziale, aby wrócić do Unicode.
Rafael S. Calsaverini,
Możesz wdrożyć tłumaczenie translacji w jednym wierszu i upewnić się, że S nie znajduje się wśród rozgałęźników dzięki:s.translate(''.join([(chr(i) if chr(i) not in seps else seps[0]) for i in range(256)])).split(seps[0])
płytom
Nie podjęto. Porównujesz jabłka i pomarańcze. ;) moje rozwiązanie w Pythonie 3 nadal działa; P i obsługuje separatory wieloznakowe. :) spróbuj zrobić to w prosty sposób bez przydzielania nowego ciągu. :) ale prawda, moja jest ograniczona do analizy parametrów wiersza poleceń, a nie na przykład książki.
pprzemek
mówisz „nie tworzy nowego ciągu”, co oznacza, że ​​działa on w miejscu na danym ciągu? Testowałem go teraz w Pythonie 2.7 i nie modyfikuje on oryginalnego ciągu znaków i zwraca nowy.
Prokop Hapala,
26

Miałem podobny dylemat i nie chciałem używać modułu „re”.

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']
pprzemek
źródło
1
Lubię to. Tylko uwaga, kolejność separatorów ma znaczenie. Przepraszam, jeśli to oczywiste.
crizCraig
2
Dlaczego nie skorzystać z remodułu, który jest zarówno o wiele szybszy, jak i wyraźniejszy (nie dlatego, że wyrażenia regularne są szczególnie wyraźne, ale ponieważ są o wiele krótsze i bezpośrednie)?
Eric O Lebigot,
13

Po pierwsze, chcę zgodzić się z innymi, że regex lub str.translate(...)oparte na nim rozwiązania są najbardziej wydajne. W moim przypadku użycie tej funkcji nie było znaczące, dlatego chciałem dodać pomysły, które wziąłem pod uwagę przy tych kryteriach.

Moim głównym celem było uogólnienie pomysłów z niektórych innych odpowiedzi w jedno rozwiązanie, które może pracować dla ciągów zawierających więcej niż tylko słowa wyrażenia regularnego (tj. Umieszczenie na czarnej liście wyraźnego podzbioru znaków interpunkcyjnych w porównaniu do znaków słów z białej listy).

Zauważ, że w każdym podejściu można również rozważyć użycie string.punctuationzamiast ręcznie zdefiniowanej listy.

Opcja 1 - re

Byłem zaskoczony, że do tej pory nie otrzymałem odpowiedzi, używa re.sub (...) . Uważam to za proste i naturalne podejście do tego problemu.

import re

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

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

W tym rozwiązaniu zagnieździłem wezwanie do re.sub(...)wewnątrz re.split(...)- ale jeśli wydajność jest krytyczna, kompilacja wyrażenia regularnego na zewnątrz może być korzystna - w moim przypadku różnica nie była znacząca, więc wolę prostotę i czytelność.

Opcja 2 - wymiana str

Jest to jeszcze kilka wierszy, ale ma tę zaletę, że można ją rozbudowywać bez konieczności sprawdzania, czy trzeba uciec pewnej postaci w wyrażeniu regularnym.

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

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

words = my_str.split()

Byłoby miło móc zamiast tego zamapować str .replace na ciąg, ale nie sądzę, że można tego dokonać za pomocą niezmiennych ciągów, a podczas mapowania na listę znaków zadziałałoby, uruchamiając każdą zamianę na każdym znaku brzmi nadmiernie. (Edycja: zobacz następną opcję dla funkcjonalnego przykładu.)

Opcja 3 - funkools.reduce

(W Python 2 reducejest dostępny w globalnej przestrzeni nazw bez importowania go z funools.)

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()
Taylor Edmiston
źródło
Hm, należy użyć innej metody str.translate- nie jest ona w stanie unicode, ale najprawdopodobniej jest szybsza niż inne metody i jako taka może być dobra w niektórych przypadkach: replacements=',-!?'; import string; my_str = my_str.translate(string.maketrans(replacements, ' ' * len(replacements)))Również tutaj obowiązkowe jest zastępowanie jako ciąg znaków, a nie krotka lub lista.
MarSoft,
@MarSoft Thanks! Wspomniałem o tym na górze odpowiedzi, ale postanowiłem nie dodawać jej, ponieważ istniejące odpowiedzi już ją dobrze omawiały.
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]

To staje się trójliniowe:

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

Wyjaśnienie

To właśnie w Haskell nazywa się monadą List. Idea stojąca za monadą polega na tym, że raz „w monadzie” zostajesz w monadzie, dopóki coś cię nie wyciągnie. Na przykład w Haskell powiedzmy, że mapujesz range(n) -> [1,2,...,n]funkcję python na Listę. Jeśli wynikiem jest lista, zostanie ona dołączona do listy w miejscu, aby uzyskać coś takiego map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]. Jest to znane jako map-append (lub mappend, a może coś takiego). Chodzi o to, że masz tę operację, którą stosujesz (dzielenie na token), i za każdym razem, gdy to robisz, dołączasz wynik do listy.

Możesz to wydzielić w funkcję i mieć tokens=string.punctuationdomyślnie.

Zalety tego podejścia:

  • To podejście (w odróżnieniu od naiwnych metod opartych na wyrażeniach regularnych) może działać z tokenami o dowolnej długości (które to wyrażenie może również wykonywać przy bardziej zaawansowanej składni).
  • Nie jesteś ograniczony do zwykłych tokenów; możesz mieć dowolną logikę zamiast każdego tokena, na przykład jeden z „tokenów” może być funkcją, która dzieli się w zależności od zagnieżdżonych nawiasów.
ninjagecko
źródło
Zgrabne rozwiązanie Haskell, ale IMO można napisać jaśniej bez mappend w Pythonie.
Vlad the Impala
@ Goose: chodziło o to, że funkcja 2-liniowa map_then_appendmoże być wykorzystana do uczynienia problemu 2-liniowym, a także wielu innych problemów o wiele łatwiejszym do napisania. Większość innych rozwiązań wykorzystuje remoduł wyrażeń regularnych , który nie jest pythonem. Ale byłem niezadowolony z tego, jak sprawiam, że moja odpowiedź wydaje się nieelegancka i rozdęta, kiedy jest naprawdę zwięzła ... Zamierzam ją edytować ...
ninjagecko,
czy to powinno działać w Pythonie tak, jak napisano? mój fragmentswynik to tylko lista znaków w ciągu (łącznie z tokenami).
Rick wspiera Monikę
@RickTeachey: działa dla mnie zarówno w python2, jak i python3.
ninjagecko
hmmmm. Być może przykład jest nieco niejednoznaczny. Próbowałem kod odpowiedź wszelkiego rodzaju różnych ways- tym konieczności fragments = ['the,string'], fragments = 'the,string'albo fragments = list('the,string')i żaden z nich nie przynoszą odpowiedniego wyjścia.
Rick wspiera Monikę
5

Spróbuj tego:

import re

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

to zostanie wydrukowane ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

Corey Goldberg
źródło
4

Użyj zamień dwa razy:

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

prowadzi do:

['11223', '33344', '33222', '3344']
jeroen
źródło
4

Lubię ponownie , ale oto moje rozwiązanie bez niego:

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 .__ zawiera__ jest metodą stosowaną przez operatora „in”. Zasadniczo jest to to samo co

lambda ch: ch in sep

ale jest tutaj wygodniejszy.

groupby pobiera nasz ciąg i funkcję. Dzieli ciąg na grupy za pomocą tej funkcji: ilekroć zmienia się wartość funkcji - generowana jest nowa grupa. Więc sep .__ zawiera__ jest dokładnie tym, czego potrzebujemy.

groupby zwraca ciąg par, gdzie para [0] jest wynikiem naszej funkcji, a para [1] jest grupą. Używając „jeśli nie k” odfiltrowujemy grupy z separatorami (ponieważ wynik sep .__ zawiera__ jest prawdziwy dla separatorów). Cóż, to wszystko - teraz mamy sekwencję grup, w których każda jest słowem (grupa jest w rzeczywistości iterowalna, więc używamy sprzężenia, aby przekonwertować ją na ciąg znaków).

To rozwiązanie jest dość ogólne, ponieważ wykorzystuje funkcję do oddzielania łańcucha znaków (możesz podzielić według dowolnych warunków). Ponadto nie tworzy pośrednich ciągów / list (możesz usunąć złączenie, a wyrażenie stanie się leniwe, ponieważ każda grupa jest iteratorem)

monitorius
źródło
4

Zamiast korzystać z funkcji ponownego modułu re.split, możesz osiągnąć ten sam wynik, stosując metodę pand serii.str.split.

Najpierw utwórz serię z powyższym ciągiem, a następnie zastosuj metodę do serii.

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

parametr pat pobiera ograniczniki i zwraca podzielony ciąg jako tablicę. Tutaj dwa separatory są przekazywane za pomocą | (lub operator). Dane wyjściowe są następujące:

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

Tarun Kumar Yellapu
źródło
1
Nie jest to kwestia gadatliwa, ale fakt importowania całej biblioteki (którą uwielbiam, BTW) w celu wykonania prostego zadania po przekształceniu łańcucha w serię panda. Niezbyt „okazyjny przyjazny”.
zar3bski
3

Ponownie zapoznałem się z Pythonem i potrzebowałem tego samego. Rozwiązanie Findall może być lepsze, ale wpadłem na to:

tokens = [x.strip() for x in data.split(',')]
Leon Starr
źródło
Sprytny, powinien działać na wszystkich angielskich konstrukcjach gramatycznych, o których mogę myśleć, z wyjątkiem kreski bez spacji - na przykład. (Obejście).
ninjagecko
3

używając maketrans i tłumacz, możesz to zrobić łatwo i porządnie

import string
specials = ',.!?:;"()<>[]#$=-/'
trans = string.maketrans(specials, ' '*len(specials))
body = body.translate(trans)
words = body.strip().split()
Ritesh Sinha
źródło
Świetna odpowiedź jak dla Pythona> = 3.6
revliscano
3

W Python 3 możesz użyć metody z PY4E - Python for Everybody .

Możemy rozwiązać oba te problemy za pomocą metody string lower, punctuationi translate. Jest translateto najbardziej subtelna z metod. Oto dokumentacja dla translate:

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

Zamień znaki fromstrna znak na tej samej pozycji tostri usuń wszystkie znaki, które są na deletestr. fromstrI tostrmoże być puste struny i deletestrparametr może zostać pominięty.

Możesz zobaczyć „interpunkcję”:

In [10]: import string

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

Na przykład:

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

Aby uzyskać więcej informacji, możesz odwołać się:

Jeremy Anifacc
źródło
2
Metody translate () i maketrans () ciągów znaków są interesujące, ale ta metoda nie „dzieli się w ogranicznikach” (lub białych spacji): na przykład: „Wystąpiło duże cave-in” niepoprawnie utworzy słowo „cavein” zamiast tego oczekiwanej „jaskini” i „w”… Tak więc nie robi to, o co pyta pytanie.
Eric O Lebigot
Tak jak to skomentował @EricLebigot. Powyższa metoda nie robi tego, o co pyta bardzo dobrze.
Jeremy Anifacc,
2

Innym sposobem na osiągnięcie tego jest użycie zestawu Natural Language Tool Kit ( nltk ).

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

To drukuje: ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

Największą wadą tej metody jest to, że musisz zainstalować pakiet nltk .

Zaletą jest to, że po otrzymaniu tokenów możesz zrobić wiele fajnych rzeczy z resztą pakietu nltk.

Tray
źródło
1

Po pierwsze, nie sądzę, że twoim intencją jest używanie interpunkcji jako ograniczników w funkcjach podziału. Twój opis sugeruje, że chcesz po prostu wyeliminować interpunkcję z powstałych ciągów.

Często się z tym spotykam, a moje zwykłe rozwiązanie nie wymaga ponownego.

Jednowierszowa funkcja lambda ze zrozumieniem listy:

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


Funkcja (tradycyjna)

Jako tradycyjna funkcja są to tylko dwa wiersze ze zrozumieniem listy (oprócz 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']

Naturalnie pozostawi również nienaruszone skurcze i dzielone słowa. Zawsze możesz użyć, text.replace("-", " ")aby zamienić łączniki w spacje przed podziałem.

Funkcja ogólna bez analizy lambda lub listy

Aby uzyskać bardziej ogólne rozwiązanie (w którym można określić znaki do wyeliminowania) i bez zrozumienia listy, otrzymujesz:

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

Oczywiście zawsze możesz uogólnić funkcję lambda na dowolny określony ciąg znaków.

cosmicFluke
źródło
1

Przede wszystkim zawsze używaj re.compile () przed wykonaniem jakiejkolwiek operacji RegEx w pętli, ponieważ działa ona szybciej niż normalna operacja.

więc dla twojego problemu najpierw skompiluj wzór, a następnie wykonaj na nim akcję.

import re
DATA = "Hey, you - what are you doing here!?"
reg_tok = re.compile("[\w']+")
print reg_tok.findall(DATA)
shrikant
źródło
1

Oto odpowiedź z wyjaśnieniem.

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'

lub w jednym wierszu możemy to zrobić w następujący sposób:

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

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

zaktualizowana odpowiedź

Tasneem Haider
źródło
1

Utwórz funkcję, która pobiera jako dane wejściowe dwa ciągi (łańcuch źródłowy, który ma zostać podzielony i ciąg listy rozdzielającej ograniczników) i wyświetla listę podzielonych słów:

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
użytkownik852006
źródło
1

Podoba mi się rozwiązanie pprzemek, ponieważ nie zakłada, że ​​ograniczniki są pojedynczymi znakami i nie próbuje używać wyrażenia regularnego (co nie działałoby dobrze, gdyby liczba separatorów musiała być szalona długa).

Oto bardziej czytelna wersja powyższego rozwiązania dla przejrzystości:

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
Everett
źródło
0

Mam taki sam problem jak @ooboo i uważam, że ten temat @ ghostdog74 zainspirował mnie, może ktoś uzna moje rozwiązanie za przydatne

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

wprowadź coś w miejscu spacji i podziel za pomocą tego samego znaku, jeśli nie chcesz rozdzielać spacji.

badas
źródło
co jeśli muszę rozdzielić za pomocą słowa?
Harsha Biyani
0

Oto mój wybór z podziałem z wieloma ogranicznikami:

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
Martlark
źródło
0

Myślę, że najlepszą odpowiedzią na twoje potrzeby jest:

\W+ może być odpowiedni dla tego przypadku, ale może nie być odpowiedni dla innych przypadków.

filter(None, re.compile('[ |,|\-|!|?]').split( "Hey, you - what are you doing here!?")
nemozhp
źródło
Zgadzam się, \wa \Wrozwiązania nie są odpowiedzią na (tytuł) pytania. Zauważ, że w twojej odpowiedzi |należy usunąć ( expr0|expr1zamiast tego myślisz [char0 char1…]). Ponadto compile()wyrażenie regularne nie jest potrzebne .
Eric O Lebigot,
0

Oto moje zdanie na ten temat ....

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']
Arindam Roychowdhury
źródło
0

replace()Najbardziej podoba mi się sposób. Poniższa procedura zmienia wszystkie separatory zdefiniowane w ciągu splitlistna pierwszy separator w, splitlista następnie dzieli tekst na tym jednym separatorze. Uwzględnia również, jeśli splitlistakurat jest to pusty ciąg. Zwraca listę słów, bez pustych ciągów.

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]
Stefan van den Akker
źródło
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

Oto użycie:

>>> s = "Hey, you - what are you doing here!?"
>>> print get_words(s)
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']
inspectorrr
źródło
0

Jeśli chcesz operacji odwracalnej (zachowaj ograniczniki), możesz użyć tej funkcji:

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
Nadav B.
źródło
0

Ostatnio musiałem to zrobić, ale chciałem funkcji, która nieco pasuje do standardowej str.splitfunkcji biblioteki , ta funkcja zachowuje się tak samo jak standardowa biblioteka, gdy jest wywoływana z argumentami 0 lub 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])

UWAGA : Ta funkcja jest przydatna tylko wtedy, gdy separatory składają się z jednego znaku (tak jak moja przypadek użycia).

Justin Fay
źródło