your programing

여러 단어 경계 구분 기호를 사용하여 문자열을 단어로 분할

lovepro 2020. 10. 2. 23:01
반응형

여러 단어 경계 구분 기호를 사용하여 문자열을 단어로 분할


제가하고 싶은 일은 상당히 일반적인 작업이라고 생각하지만 웹에서 참조를 찾지 못했습니다. 구두점이있는 텍스트가 있고 단어 목록이 필요합니다.

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

해야한다

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

그러나 Python str.split()은 하나의 인수로만 작동하므로 공백으로 나눈 후 모든 단어에 구두점이 있습니다. 어떤 아이디어?


정규식이 정당화되는 경우 :

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

re.split ()

re.split (패턴, 문자열 [, maxsplit = 0])

패턴 발생으로 문자열을 분할합니다. 캡처 링 괄호가 패턴에 사용되는 경우 패턴에있는 모든 그룹의 텍스트도 결과 목록의 일부로 반환됩니다. maxsplit이 0이 아니면 최대 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.']

정규 표현식없이이를 수행하는 또 다른 빠른 방법은 아래와 같이 문자를 먼저 바꾸는 것입니다.

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

답변이 너무 많지만 질문의 제목 이 문자 그대로 요구하는 것을 효율적으로 수행하는 솔루션을 찾을 수 없습니다 (여러 개의 가능한 구분 기호로 분할-대신 많은 답변이 단어가 아닌 다른 것을 제거함). 그래서 다음은 파이썬의 표준적이고 효율적인 re모듈 에 의존하는 제목의 질문에 대한 답입니다 .

>>> 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모듈은 "손으로"파이썬 루프와 테스트를하는 것보다 (속도와 간결에서) 훨씬 더 효율적입니다!


정규식이없는 또 다른 방법

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

Pro-Tip : string.translatePython이 가진 가장 빠른 문자열 연산에 사용 합니다.

증거 ...

첫째, 느린 방법 (죄송합니다 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.translateC로 구현되며 Python의 많은 문자열 조작 함수와 달리 새 문자열을 생성 string.translate 하지 않습니다 . 따라서 문자열 대체를 위해 얻을 수있는 것만 큼 빠릅니다.

하지만이 마법을 수행하려면 번역 테이블이 필요하기 때문에 약간 어색합니다. maketrans()편의 기능 으로 번역 표를 만들 수 있습니다 . 여기서 목표는 원하지 않는 모든 문자를 공백으로 변환하는 것입니다. 일대일 대체품. 다시 말하지만, 새로운 데이터는 생성되지 않습니다. 그래서 이것은 빠르다 !

다음으로 우리는 좋은 오래된 split(). split()기본적으로 모든 공백 문자에서 작동하며 분할을 위해 함께 그룹화됩니다. 결과는 원하는 단어 목록이됩니다. 그리고이 접근법은 re.findall()! 보다 거의 4 배 빠릅니다 .


비슷한 딜레마가 있었고 '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']

첫째, 정규식 또는 str.translate(...)기반 솔루션이 가장 성능이 좋다는 다른 사람들과 동의하고 싶습니다 . 제 사용 사례에서는이 기능의 성능이 중요하지 않았기 때문에 해당 기준으로 고려한 아이디어를 추가하고 싶었습니다.

내 주요 목표는 다른 답변 중 일부의 아이디어를 정규식 단어 이상을 포함하는 문자열에 대해 작동 할 수있는 하나의 솔루션으로 일반화하는 것이 었습니다 (즉, 구두점 문자의 명시적인 하위 집합을 블랙리스트에 추가하는 것과 단어 문자를 화이트리스트에 추가하는 것).

모든 접근 방식에서 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(...)했지만 성능이 중요하다면 외부 정규 표현식을 컴파일하는 것이 도움이 될 수 있습니다. 제 사용 사례에서는 그 차이가 크지 않았기 때문에 단순성과 가독성을 선호합니다.

옵션 2-str. 교체

이것은 몇 줄 더 있지만 정규식에서 특정 문자를 이스케이프해야하는지 여부를 확인하지 않고도 확장 할 수 있다는 이점이 있습니다.

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에서는 reducefunctools에서 가져 오지 않고도 전역 네임 스페이스에서 사용할 수 있습니다.)

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

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]

그러면 이것은 3 줄이됩니다.

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

설명

이것은 Haskell에서 List 모나드로 알려진 것입니다. 모나드의 배후에있는 아이디어는 일단 "모나드에서"당신은 무언가가 당신을 데려 갈 때까지 "모나드에 머물러"있다는 것입니다. 예를 들어 Haskell에서 range(n) -> [1,2,...,n]List를 통해 Python 함수 를 매핑한다고 가정합니다 . 결과가 List 인 경우 List in-place에 추가되므로 map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]. 이것은 map-append (또는 mappend 또는 이와 유사한 것)로 알려져 있습니다. 여기서 아이디어는 적용하는 (토큰에 분할)이 작업을 가지고 있고, 그렇게 할 때마다 결과를 목록에 결합한다는 것입니다.

이것을 함수로 추상화 tokens=string.punctuation하고 기본적으로 가질 수 있습니다 .

이 접근 방식의 장점 :

  • 이 접근 방식 (순진한 정규식 기반 접근 방식과는 달리)은 임의 길이 토큰 (정규식은 고급 구문으로도 수행 할 수 있음)과 함께 작동 할 수 있습니다.
  • 당신은 단순한 토큰에 국한되지 않습니다. 각 토큰 대신 임의의 논리를 가질 수 있습니다. 예를 들어 "토큰"중 하나는 중첩 된 괄호에 따라 분할되는 함수가 될 수 있습니다.

이 시도:

import re

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

이것은 인쇄됩니다 ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']


두 번 교체 사용 :

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

결과 :

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

나는 re를 좋아 하지만 여기에 그것없이 내 솔루션이 있습니다.

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 .__ contains__ 는 'in'연산자가 사용하는 메서드입니다. 기본적으로 다음과 같습니다.

lambda ch: ch in sep

하지만 여기서 더 편리합니다.

groupby 는 문자열과 함수를 가져 옵니다 . 이 함수를 사용하여 문자열을 그룹으로 분할합니다. 함수 값이 변경 될 때마다 새 그룹이 생성됩니다. 그래서 sep .__ contains__ 는 정확히 우리에게 필요한 것입니다.

groupby 는 쌍의 시퀀스를 반환합니다. 여기서 pair [0]은 함수의 결과이고 pair [1]은 그룹입니다. 'if not k'를 사용하여 구분자로 그룹을 필터링합니다 (구분자에서 sep .__ contains__ 의 결과 가 True 이기 때문에 ). 그게 다입니다. 이제 각 그룹이 단어 인 일련의 그룹이 있습니다 (그룹은 실제로 반복 가능하므로 join사용 하여 문자열로 변환합니다).

이 솔루션은 문자열을 분리하는 함수를 사용하기 때문에 매우 일반적입니다 (필요한 조건으로 분할 할 수 있음). 또한 중간 문자열 / 목록을 생성하지 않습니다 ( 조인 을 제거 할 수 있으며 각 그룹이 반복기이기 때문에 표현식이 지연됩니다).


re 모듈 함수 re.split을 사용하는 대신 pandas의 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!?]


나는 파이썬에 대해 다시 알고 있으며 같은 것이 필요했습니다. findall 솔루션이 더 좋을 수도 있지만 다음과 같이 생각해 냈습니다.

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

Python 3에서는 PY4E-Python for Everybody 의 메소드를 사용할 수 있습니다 .

우리는 문자열 방법을 사용하여 이러한 문제를 모두 해결할 수있는 lower, punctuation하고 translate. translate방법 중 가장 미묘하다. 다음에 대한 문서는 다음과 같습니다 translate.

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

의 문자를 교체 fromstr에서 같은 위치에있는 문자 tostr와에있는 모든 문자를 삭제 deletestr. fromstrtostr빈 문자열이 될 수 있으며, deletestr매개 변수를 생략 할 수 있습니다.

"구두점"을 볼 수 있습니다.

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

자세한 내용은 다음을 참조하십시오.


maketrans와 번역을 사용하면 쉽고 깔끔하게 할 수 있습니다.

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

이를 달성하는 또 다른 방법은 자연어 도구 키트 ( 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 패키지 로 많은 재미있는 일 을 할 수 있다는 것 입니다.


우선, 나는 당신의 의도가 실제로 분할 함수에서 구분 기호로 구두점을 사용하는 것이라고 생각하지 않습니다. 귀하의 설명은 결과 문자열에서 구두점을 제거하고 싶다고 제안합니다.

나는 이것을 꽤 자주 접하며 일반적인 솔루션에는 re가 필요하지 않습니다.

목록 이해력이있는 한 줄짜리 람다 함수 :

(필요 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']


기능 (기존)

전통적인 함수로서 이것은 목록 이해력이있는 두 줄뿐입니다 (추가 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("-", " ")분할 전에 항상를 사용 하여 하이픈을 공백으로 바꿀 수 있습니다 .

Lambda 또는 List Comprehension이없는 일반 기능

보다 일반적인 솔루션 (제거 할 문자를 지정할 수 있음) 및 목록 이해없이 다음을 얻을 수 있습니다.

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

물론 람다 함수를 지정된 문자열로도 일반화 할 수 있습니다.


우선 정규 작업보다 빠르게 작동하므로 루프에서 RegEx 작업을 수행하기 전에 항상 re.compile ()을 사용하십시오.

따라서 문제에 대해 먼저 패턴을 컴파일 한 다음 이에 대한 조치를 수행하십시오.

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

여기에 몇 가지 설명이있는 대답이 있습니다.

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

업데이트 된 답변


Create a function that takes as input two strings (the source string to be split and the splitlist string of delimiters) and outputs a list of split words:

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

got same problem as @ooboo and find this topic @ghostdog74 inspired me, maybe someone finds my solution usefull

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

input something in space place and split using same character if you dont want to split at spaces.


Here is my go at a split with multiple deliminaters:

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

I think the following is the best answer to suite your needs :

\W+ maybe suitable for this case, but may not be suitable for other cases.

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

Heres my take on it....

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

I like the replace() way the best. The following procedure changes all separators defined in a string splitlist to the first separator in splitlist and then splits the text on that one separator. It also accounts for if splitlist happens to be an empty string. It returns a list of words, with no empty strings in it.

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]

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

Here is the usage:

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

If you want a reversible operation (preserve the delimiters), you can use this function:

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

I recently needed to do this but wanted a function that somewhat matched the standard library str.split function, this function behaves the same as standard library when called with 0 or 1 arguments.

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

NOTE: This function is only useful when your separators consist of a single character (as was my usecase).


I like pprzemek's solution because it does not assume that the delimiters are single characters and it doesn't try to leverage a regex (which would not work well if the number of separators got to be crazy long).

Here's a more readable version of the above solution for clarity:

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

참고URL : https://stackoverflow.com/questions/1059559/split-strings-into-words-with-multiple-word-boundary-delimiters

반응형