将字符串拆分为具有多个单词边界分隔符的单词

我想我想做的是一个相当常见的任务,但我在网上找不到参考资料。我有标点符号的文本,我想要一个单词列表。

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

应该是

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

但是Python的str.split()只适用于一个参数,所以在我用空格拆分后,我有所有带有标点符号的单词。有什么想法吗?

890178 次浏览

正则表达式被证明是正确的情况:

import reDATA = "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(模式,字符串[, max拆分=0])

按模式的出现次数拆分字符串。如果模式中使用捕获括号,则模式中所有组的文本也会作为结果列表的一部分返回。如果max的值不为零,则最多发生max的拆分,并且字符串的其余部分作为列表的最后一个元素返回。(不兼容注意:在最初的Python 1.5版本中,max的部分被忽略。这在以后的版本中得到了修复。)

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

试试这个:

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

这将打印['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

另一种方法是使用自然语言工具包(NLTK)。

import nltkdata= "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包的其余部分执行很多好玩的东西操作。

另一种方式,没有regex

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

我有一个类似的困境,不想使用'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']

遇到与@ooboos相同的问题并找到此主题@ghostdog74启发了我,也许有人觉得我的解决方案有用

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

如果您不想在空格处拆分,请在空格place中输入一些内容并使用相同的字符拆分。

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中,假设你将pythonrange(n) -> [1,2,...,n]函数映射到List上。如果结果是一个List,它将被就地附加到List中,所以你会得到类似于map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]的东西。这被称为map-append(或mappend,或者类似的东西)。这里的想法是,你已经应用了这个操作(在令牌上拆分),每当你这样做时,你都会将结果加入列表。

您可以将其抽象为一个函数,默认情况下具有tokens=string.punctuation

这种方法的优点:

  • 这种方法(与简单的基于正则表达式的方法不同)可以使用任意长度的令牌(正则表达式也可以使用更高级的语法)。
  • 您不仅限于标记;您可以使用任意逻辑来代替每个标记,例如,其中一个“标记”可以是根据嵌套括号的方式进行拆分的函数。

这是我在使用多个分隔符进行拆分时的操作:

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

另一种不使用regexp的快速方法是首先替换字符,如下所示:

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

我认为以下是满足您需求的最佳答案:

\W+可能适合这种情况,但可能不适合其他情况。

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

使用替换两次:

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

结果:

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

我重新熟悉Python并需要同样的事情。这个解决方案可能更好,但我想出了这个:

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

专业提示:使用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()将对所有空格字符进行操作,将它们分组在一起进行拆分。结果将是您想要的单词列表。这种方法几乎比re.findall()快4倍!

这是我对它的看法……

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 s1s1 = s1 + cif 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']

我喜欢关于,但这是我没有它的解决方案:

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

__contains__是'in'运算符使用的方法。基本上它与

lambda ch: ch in sep

但在这里更方便。

Groupby获取我们的字符串和函数。它使用该函数将字符串拆分为组:每当函数的值发生变化时-都会生成一个新的组。所以,__contains__正是我们需要的。

Groupby返回一个对序列,其中对[0]是我们函数的结果,对[1]是一个组。使用如果不是k,我们过滤掉带有分隔符的组(因为__contains__的结果在分隔符上为True)。好吧,这就是全部-现在我们有一个组序列,其中每个组都是一个单词(组实际上是一个可迭代的,所以我们使用加入将其转换为字符串)。

这个解决方案很通用,因为它使用函数来分隔字符串(你可以按你需要的任何条件拆分)。此外,它不会创建中间字符串/列表(你可以删除加入,表达式会变得懒惰,因为每个组都是迭代器)

def get_words(s):l = []w = ''for c in s.lower():if c in '-!?,. ':if w != '':l.append(w)w = ''else:w = w + cif 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']

我最喜欢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]

这么多答案,但我找不到任何解决方案能有效地解决问题中的标题字面上所要求的问题(分裂在多个可能的分隔符上——相反,许多答案分裂在任何不是单词的东西上,这是不同的)。所以这是标题中问题的答案,它依赖于Python标准且高效的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模块比“手动”执行Python循环和测试要高效得多(在速度和简洁性方面)!

首先,我不认为你的意图实际上是在拆分函数中使用标点符号作为分隔符。你的描述表明你只是想从结果字符串中消除标点符号。

我经常遇到这个问题,我通常的解决方案不需要re。

带列表理解的单行lambda函数:

(需要import string):

split_without_punc = lambda text : [word.strip(string.punctuation) for word intext.split() if word.strip(string.punctuation) != '']
# Call functionsplit_without_punc("Hey, you -- what are you doing?!")# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']


功能(传统)

作为一个传统的函数,这仍然只有两行带有列表理解(除了import string):

def split_without_punctuation2(text):
# Split by whitespacewords = text.split()
# Strip punctuation from each wordreturn [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("-", " ")在拆分之前将连字符变为空格。

通用函数w/o Lambda或列表理解

对于更通用的解决方案(您可以在其中指定要消除的字符),并且没有列表理解,您将获得:

def split_without(text: str, ignore: str) -> list:
# Split by whitespacesplit_string = text.split()
# Strip any characters in the ignore string, and ignore empty stringswords = []for word in split_string:word = word.strip(ignore)if word != '':words.append(word)
return words
# Situation-specific call to general functionimport stringfinal_text = split_without("Hey, you - what are you doing?!", string.punctuation)# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

当然,您也可以将lambda函数泛化为任何指定的字符串。

首先,在循环中执行任何RegEx操作之前始终使用re.compile(),因为它的工作速度比正常操作快。

因此,对于您的问题,首先编译模式,然后对其执行操作。

import reDATA = "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 providednew_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']

更新的答案

首先,我想同意其他人的观点,即基于正则表达式或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(...)中——但是如果性能至关重要,在外面编译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()

创建一个函数,将两个字符串(要拆分的源字符串和分隔符的拆分列表字符串)作为输入,并输出拆分词列表:

def split_string(source, splitlist):output = []  # output list of cleaned wordsatsplit = Truefor char in source:if char in splitlist:atsplit = Trueelse:if atsplit:output.append(char)  # append new word after splitatsplit = Falseelse:output[-1] = output[-1] + char  # continue copying characters until next splitreturn output

如果你想要一个可逆的操作(保留分隔符),你可以使用这个函数:

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

使用maketrans和翻译,您可以轻松整齐地完成它

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

在Python 3中,您可以使用PY4E-面向所有人的Python中的方法。

我们可以通过使用字符串方法lowerpunctuationtranslate来解决这两个问题。translate是最微妙的方法。这是translate的留档:

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

fromstr中的字符替换为tostr中相同位置的字符,并删除deletestr中的所有字符。fromstrtostr可以是空字符串,deletestr参数可以省略。

你可以看到“标点符号”:

In [10]: import string
In [11]: string.punctuationOut[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']

有关更多信息,您可以参考:

与使用re模块函数不同re.split您可以使用series.str.split方法获得相同的结果。

首先,使用上述字符串创建一个系列,然后将该方法应用于该系列。

thestring=pd. Series(“嘿,你-你在这里干什么!?”)thestring.str.split(拍 = ',|-')

参数帕特接受分隔符并将拆分字符串作为数组返回。这里使用|(或运算符)传递两个分隔符。输出如下:

【喂,你,你在这里干什么!?】

我最近需要这样做,但想要一个与标准库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])

:此函数仅在分隔符由单个字符组成时有用(就像我的用例一样)。

我喜欢pprzemek的解决方案,因为它没有假设分隔符是单个字符,也没有尝试利用正则表达式(如果分隔符的数量很长,这将无法正常工作)。

为了清晰起见,以下是上述解决方案的更易读版本:

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

我必须想出自己的解决方案,因为到目前为止我测试的所有东西都在某个时候失败了。

>>> import re>>> def split_words(text):...     rgx = re.compile(r"((?:(?<!'|\w)(?:\w-?'?)+(?<!-))|(?:(?<='|\w)(?:\w-?'?)+(?=')))")...     return rgx.findall(text)

它似乎工作得很好,至少对于下面的例子。

>>> split_words("The hill-tops gleam in morning's spring.")['The', 'hill-tops', 'gleam', 'in', "morning's", 'spring']>>> split_words("I'd say it's James' 'time'.")["I'd", 'say', "it's", "James'", 'time']>>> split_words("tic-tac-toe's tic-tac-toe'll tic-tac'tic-tac we'll--if tic-tac")["tic-tac-toe's", "tic-tac-toe'll", "tic-tac'tic-tac", "we'll", 'if', 'tic-tac']>>> split_words("google.com email@google.com split_words")['google', 'com', 'email', 'google', 'com', 'split_words']>>> split_words("Kurt Friedrich Gödel (/ˈɡɜːrdəl/;[2] German: [ˈkʊɐ̯t ˈɡøːdl̩] (listen);")['Kurt', 'Friedrich', 'Gödel', 'ˈɡɜːrdəl', '2', 'German', 'ˈkʊɐ', 't', 'ˈɡøːdl', 'listen']>>> split_words("April 28, 1906 – January 14, 1978) was an Austro-Hungarian-born Austrian...")['April', '28', '1906', 'January', '14', '1978', 'was', 'an', 'Austro-Hungarian-born', 'Austrian']