我想我想做的是一项相当常见的任务,但我在网上找不到任何参考资料。我有带标点符号的文本,我想要一个单词列表。
"Hey, you - what are you doing here!?"
应该是
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']
但Python的str.split()只对一个参数有效,所以在用空格拆分后,所有单词都带有标点符号。有什么想法吗?
我想我想做的是一项相当常见的任务,但我在网上找不到任何参考资料。我有带标点符号的文本,我想要一个单词列表。
"Hey, you - what are you doing here!?"
应该是
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']
但Python的str.split()只对一个参数有效,所以在用空格拆分后,所有单词都带有标点符号。有什么想法吗?
当前回答
使用maketrans和translate,您可以轻松、整洁地完成
import string
specials = ',.!?:;"()<>[]#$=-/'
trans = string.maketrans(specials, ' '*len(specials))
body = body.translate(trans)
words = body.strip().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.ttranslate不会生成新字符串。所以它的速度和字符串替换一样快。
不过,这有点尴尬,因为它需要一个翻译表来实现这一魔术。您可以使用maketrans()方便函数创建转换表。这里的目标是将所有不需要的字符转换为空格。一换一的替代品。同样,不会产生新数据。所以这很快!
接下来,我们使用旧的split()。默认情况下,split()将对所有空白字符进行操作,将它们分组以进行拆分。结果将是您想要的单词列表。而且这种方法几乎比re.findall()快4倍!
创建一个函数,将两个字符串(要拆分的源字符串和分隔符的拆分列表字符串)作为输入,并输出拆分单词列表:
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
我必须想出自己的解决方案,因为我迄今为止测试的所有东西都在某一点上失败了。
>>> 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']
试试看:
import re
phrase = "Hey, you - what are you doing here!?"
matches = re.findall('\w+', phrase)
print matches
这将打印['Hey','you','what','are','you','doing','here']
首先,我想同意其他人的观点,即基于正则表达式或str.translate(…)的解决方案是最具性能的。对于我的用例,这个函数的性能并不重要,所以我想添加一些我认为符合这个标准的想法。
我的主要目标是将其他一些答案中的想法归纳为一个解决方案,该解决方案可以适用于包含不仅仅是正则表达式单词的字符串(即,将标点符号字符的显式子集列入黑名单,而不是将单词字符列入白名单)。
注意,在任何方法中,也可以考虑使用string.p标点符号代替手动定义的列表。
选项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.replace
这是几行,但它的优点是可以扩展,而不必检查是否需要转义正则表达式中的某个字符。
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在全局命名空间中可用,而无需从functools导入。)
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()