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

oob*_*boo 631 python string split

我认为我想做的是一项相当普遍的任务,但我在网上找不到任何参考.我有带标点符号的文字,我想要一个单词列表.

"Hey, you - what are you doing here!?"
Run Code Online (Sandbox Code Playgroud)

应该

['hey', 'you', 'what', 'are', 'you', 'doing', 'here']
Run Code Online (Sandbox Code Playgroud)

但是Python str.split()只能使用一个参数,所以在用空格分割之后,我所有的单词都带有标点符号.有任何想法吗?

gim*_*mel 536

re.split()

re.split(pattern,string [,maxsplit = 0])

按模式的出现拆分字符串.如果在模式中使用捕获括号,则模式中所有组的文本也将作为结果列表的一部分返回.如果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.']
Run Code Online (Sandbox Code Playgroud)

  • 现在,只要我能记住`\ w`,`\ W`,`\ s`和`\ S`之间的区别.无论谁认为旗帜的大写都应该颠倒它的意义,都需要从脑袋中射出. (57认同)
  • 这个解决方案的优点是很容易适应分裂下划线,但findall解决方案没有:print re.split("\ W + | _","Testing this_thing")'产生:['测试','这个' ,'东西'] (13认同)
  • @ArtOfWarfare 通常使用 `shift` 键来做相反的事情。`ctrl+z` 撤消 vs. `ctrl+shift+z` 重做。因此,`shift w` 或`W` 将与`w` 相反。 (5认同)
  • 字符串拆分的一个常见用例是从最终结果中删除空字符串条目。用这种方法可以做到吗?re.split('\W+', ' abc ') 结果为 ['', 'a', 'b', 'c', ''] (2认同)
  • 这个答案应该是最重要的——它是唯一一个准确回答问题标题的答案。 (2认同)
  • 这应该是“r”\W+“(原始字符串)吗? (2认同)

Ric*_*dle 442

正则表达式合理的情况:

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

  • 这不是问题的答案.这是对不同问题的回答,恰好适用于这种特殊情况.就好像有人问"我怎么左转"而最高投票的答案是"接下来的三个转弯".它适用于某些交叉路口,但它没有给出所需的答案.具有讽刺意味的是,答案*是`re`,而不是`findall`.下面给出`re.split()`的答案是优越的. (303认同)
  • 正则表达式起初可能令人生畏,但功能非常强大.正则表达式'\ w +'表示"重复一次或多次的单词字符(az等)".这里有一个关于Python正则表达式的HOWTO:http://www.amk.ca/python/howto/regex/ (26认同)
  • 这不适用于包含连字符(`-`)的单词. (9认同)
  • @JesseDhillon"将由一系列单词字符组成的所有子串"和"在由一系列非单词字符组成的所有子串上拆分"实际上只是表达相同操作的不同方式; 我不确定你为什么要打电话给上级. (4认同)
  • @TMWP:撇号意味着像"不"这样的词被视为一个单词,而不是被分成"不要"和"t". (3认同)
  • 谢谢.但仍然感兴趣 - 如何实现此模块中使用的算法?为什么它不出现在字符串模块中? (2认同)

Lou*_* LC 352

另一种没有正则表达式的快速方法是首先替换字符,如下所示:

>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']
Run Code Online (Sandbox Code Playgroud)

  • 快速而肮脏,但完美适合我的情况(我的分隔符是一个小的,已知的设置) (67认同)
  • 我认为这比RE更明确,所以它是一种友好的noob友好.有时不需要对所有事情进行一般性解决 (10认同)
  • 适用于无法访问RE库的情况,例如某些小型微控制器.:-) (6认同)

Eri*_*got 287

如此多的答案,但我找不到任何解决方案,有效地解决了问题的标题字面要求(分裂多个可能的分隔符 - 相反,许多答案删除任何不是一个单词的东西,这是不同的).所以这里是标题中问题的答案,它依赖于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']
Run Code Online (Sandbox Code Playgroud)

哪里:

  • […]比赛一个内所列的分离器,
  • \-在正则表达式是在这里以防止特殊解释-为字符范围指示器(如在A-Z),
  • +跳过一个或多个定界符(它可以省略感谢filter(),但是这将不必要地产生匹配隔板之间空字符串),并
  • filter(None, …) 删除可能由前导和尾随分隔符创建的空字符串(因为空字符串具有false布尔值).

re.split()正如问题标题中所要求的那样,这恰好"与多个分隔符分开".

此解决方案还可以免受其他一些解决方案中的非ASCII字符问题的影响(请参阅ghostdog74的答案的第一条评论).

re模块比使用Python循环和"手动"测试更有效(速度和简洁)!

  • 这个答案不会分隔分隔符(来自一组多个分隔符):而是分裂为任何不是字母数字的分隔符.也就是说,我同意原始海报的意图可能只是保留单词,而不是删除一些标点符号. (17认同)
  • "我找不到任何解决方案能够有效地解决问题的标题问题" - 第二个答案就是这样,发布于5年前:http://stackoverflow.com/a/1059601/2642204. (3认同)
  • @EOL:我刚刚意识到我对你的评论感到困惑"这个答案没有分裂......"我认为"这个"是指你的re.split答案,但我现在意识到你的意思是gimel的答案.我认为这个答案(我正在评论的答案)是最好的答案:) (3认同)
  • 具有讽刺意味的是,这个答案没有获得最多选票的原因......有技术上正确的答案,然后是原始请求者正在寻找的内容(他们的意思而不是他们所说的)。这是一个很好的答案,我在需要时复制了它。然而,对我来说,评分最高的答案解决了一个与海报工作非常相似的问题,快速、干净且代码最少。如果一个答案同时发布了两种解决方案,我会投 4 票。哪一个更好取决于你实际尝试做什么(而不是被问到的“如何做”任务)。:-) (2认同)

gho*_*g74 55

另一种方式,没有正则表达式

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()
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案实际上比接受的解决方案更好.它没有ASCII字符,请尝试"嘿,你 - 你在这里做什么玛丽亚!?".接受的解决方案不适用于前面的示例. (8认同)
  • 我认为这里有一个小问题......你的代码将附加用标点符号分隔的字符,因此不会将它们分开......如果我没错,你的最后一行应该是:`''.join( [o如果不是o在string.punctuation中,否则''为o in s]).split()` (4认同)
  • 这个解决方案非常低效:首先将列表解构为单个字符,然后对原始字符串中的每个单个字符执行_whole_标点符号集,然后将字符组合回来,然后再次拆分.与基于正则表达式的解决方案相比,所有这些"运动"也非常复杂:即使速度在给定的应用中无关紧要,也不需要复杂的解决方案.由于`re`模块是标准的并且兼具易读性和速度,我不明白为什么应该避开它. (4认同)

Dav*_*ave 39

专业提示: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
Run Code Online (Sandbox Code Playgroud)

接下来,我们使用re.findall()(由建议的答案给出).快多了:

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

最后,我们使用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
Run Code Online (Sandbox Code Playgroud)

说明:

string.translate在C中实现,与Python中的许多字符串操作函数不同,string.translate 不会产生新的字符串.所以它的速度和字符串替换一样快.

但是,它有点尴尬,因为它需要一个翻译表才能做到这一点.您可以使用maketrans()便利功能制作翻译表.这里的目标是将所有不需要的字符转换为空格.一对一的替代品.同样,没有产生新数据.所以这很快!

接下来,我们使用旧的split().split()默认情况下,它将对所有空白字符进行操作,将它们组合在一起以进行拆分.结果将是您想要的单词列表.这种方法几乎快了4倍re.findall()!

  • 我在这里做了一个测试,如果你需要使用unicode,请使用`patt = re.compile(ur'\ w +',re.UNICODE); patt.findall(S)`比translate更快,因为你必须在应用transform之前对字符串进行编码,并在拆分后解码列表中的每个项目以返回unicode. (4认同)

ppr*_*mek 24

有点迟到的答案:),但我有一个类似的困境,并不想使用'重'模块.

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']
Run Code Online (Sandbox Code Playgroud)

  • 为什么不使用 `re` 模块,它既快速又清晰(不是正则表达式特别清晰,而是因为它更短更直接)? (2认同)

Tay*_*ton 12

首先,我想与其他人一致认为正则表达式或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())
Run Code Online (Sandbox Code Playgroud)

在这个解决方案中,我嵌入了对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()
Run Code Online (Sandbox Code Playgroud)

能够将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()
Run Code Online (Sandbox Code Playgroud)


nin*_*cko 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]
Run Code Online (Sandbox Code Playgroud)

然后这变成了三个班轮:

fragments = [text]
for token in tokens:
    fragments = join(f.split(token) for f in fragments)
Run Code Online (Sandbox Code Playgroud)

说明

这就是Haskell被称为List monad的内容.monad背后的想法是,一旦"在monad"中你"留在monad",直到有什么东西把你带出去.例如在Haskell中,假设您将python range(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默认具有.

这种方法的优点:

  • 这种方法(与朴素的基于正则表达式的方法不同)可以使用任意长度的令牌(正则表达式也可以使用更高级的语法).
  • 你不仅仅局限于令牌; 您可以使用任意逻辑代替每个标记,例如,"标记"之一可以是根据嵌套括号的方式分割的函数.


mon*_*ius 7

我喜欢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]
Run Code Online (Sandbox Code Playgroud)

sep.__contains__是 'in' 运算符使用的一种方法。基本上是一样的

lambda ch: ch in sep
Run Code Online (Sandbox Code Playgroud)

但这里更方便。

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

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

这个解决方案非常通用,因为它使用一个函数来分隔字符串(您可以根据需要的任何条件进行拆分)。此外,它不会创建中间字符串/列表(您可以删除join并且表达式将变得懒惰,因为每个组都是一个迭代器)


小智 6

使用替换两次:

a = '11223FROM33344INTO33222FROM3344'
a.replace('FROM', ',,,').replace('INTO', ',,,').split(',,,')
Run Code Online (Sandbox Code Playgroud)

结果是:

['11223', '33344', '33222', '3344']
Run Code Online (Sandbox Code Playgroud)


Cor*_*erg 5

尝试这个:

import re

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

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