我认为我想做的是一个相当常见的任务,但我在网上找不到任何参考资料。我有带标点符号的文本,我想要一个单词列表。
"Hey, you - what are you doing here!?"
应该是。['hey', 'you', 'what', 'are', 'you', 'doing', 'here']
但是 Python 的 str.split()
只能使用一个参数,所以在使用空格分割后会将标点符号与单词分开。有什么解决办法吗?
通过模式匹配来分割字符串。如果在模式中使用了捕获括号,则模式中所有组的文本也会作为结果列表的一部分返回。如果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.']
ctrl+z
撤消与 ctrl+shift+z
重做。因此,“shift w”或“W”将是“w”的相反操作。 - Frank Velr'\W+'
(原始字符串)吗? - nyanpasu64正则表达式被证明是合理的情况:
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
中,只是不在findall
中。下面给出的使用re.split()
的答案更好。 - Jesse Dhillon另一个不使用正则表达式的快速方法是首先替换字符,代码如下:
>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']
>>> 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, ...)
去除由前导和尾随分隔符创建的空字符串(因为空字符串具有 false 布尔值)。这个 re.split()
函数可以精确地“按多个分隔符拆分”,正如问题标题所要求的那样。
此解决方案还免疫某些其他解决方案中出现的单词中的非 ASCII 字符的问题(请参见对 ghostdog74 的答案的第一条评论)。
与手动编写 Python 循环和测试相比,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()
“嘿,你-玛丽亚,你在这里干什么?!”
。被接受的解决方案将无法处理前面的例子。 - Christopher Ramírez''.join([o if not o in string.punctuation else ' ' for o in s]).split()
- cedbeuo for o in s if (o in not string.punctuation or o == "'")
,但是如果我们还加入了cedbeu的补丁,这将变得过于复杂,无法成为一行代码。 - Daniel Hre
模块是标准的,可以提供可读性和速度,我不明白为什么要避免使用它。 - Eric O. Lebigot专业提示:使用 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倍!
patt = re.compile(ur'\w+', re.UNICODE); patt.findall(S)
比使用 translate 更快,因为您必须在应用转换之前对字符串进行编码,并在拆分后解码列表中的每个项目以返回 Unicode。 - Rafael S. Calsaverinis.translate(''.join([(chr(i) if chr(i) not in seps else seps[0]) for i in range(256)])).split(seps[0])
- hobsstring.translate
和string.maketrans
在Python 3中不可用,只有在Python 2中才有。 - Futal我面临类似的困境,不想使用'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']
re
模块呢?它既快速又清晰(虽然正则表达式本身并不特别清晰),但是因为更简短和直接。 - Eric O. Lebigotre
模块,特别是如果你进行嵌入式开发,那么你需要尽可能地精简。 - pprzemek首先,我同意其他人的观点,即基于正则表达式或 str.translate(...)
的解决方案最为高效。对于我的用例,这个函数的性能并不重要,因此我想添加一些符合这个标准的想法。
我的主要目标是将其他答案中的思路概括成一个可以适用于包含除正则表达式单词外更多内容的字符串的解决方案(例如,将显式子集的标点符号列入黑名单与将单词字符列入白名单进行过滤)。
注意,在任何方法中,都可以考虑使用 string.punctuation
来代替手动定义列表。
我很惊讶到目前为止没有答案使用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(...)
内部 - 但如果性能很关键,编译正则表达式可能会更有益 - 对于我的用例来说,差异不大,所以我更喜欢简单和可读性。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
映射到字符串本身会很好,但我认为在不可变字符串上无法实现。虽然映射到字符列表可以工作,但对每个字符运行每次替换听起来有些过度了。(编辑:请参阅下一个选项以获取功能示例。)
(在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()
str.translate
- 它不支持Unicode,但很可能比其他方法更快,因此在某些情况下可能很好:
replacements = ',-!?';import string;my_str = my_str.translate(string.maketrans(replacements,' ' * len(replacements)))
此外,这里必须将替换字符作为一个字符串,而不是元组或列表。 - MarSoftjoin = 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)。单子背后的理念是一旦「进入单子」,只要没有特殊操作将你带出来,你就一直处于单子中。例如,在 Haskell 中,如果你将 python 的 range(n) -> [1,2,.. .,n]
函数映射到列表上,若结果也是列表,则会将其原地附加到列表中,你会得到类似这样的东西:map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]
。这被称为地图-append(或 mappend,或可能是这样的东西)。这里的意思是,你有一个正在应用的操作(按令牌拆分),每当你执行该操作时,就将结果连接到列表中。tokens=string.punctuation
。map_then_append
可以用来将一个问题变成两行代码,同时也可以让许多其他问题更容易编写。大多数其他解决方案使用正则表达式re
模块,这不是Python的特性。但我对我的答案看起来不够优雅和臃肿感到不满,实际上它非常简洁...我要进行编辑... - ninjageckofragments = ['the,string']
、fragments = 'the,string'
或 fragments = list('the,string')
,但它们都没有产生正确的输出。 - Rick我喜欢使用 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将其转换为字符串)。
这种解决方案非常通用,因为它使用函数来分隔字符串(您可以按任何需要拆分)。此外,它不会创建中间字符串/列表(您可以删除join,而表达式将变得惰性,因为每个组都是迭代器)。
str.split()
方法也可以不传任何参数来使用。 - Ivan Vinogradov