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

时间:2009-06-29 17:49:35

标签: python string split

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

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

应该是

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

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

32 个答案:

答案 0 :(得分:497)

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

答案 1 :(得分:408)

正则表达式合理的情况:

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

答案 2 :(得分:331)

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

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

答案 3 :(得分:255)

这么多的答案,但是我找不到任何能够有效地解决问题的标题的解决方案(拆分多个可能的分隔符 - 相反,许多答案会删除任何不是一个词,这是不同的)。所以这里是标题中问题的答案,它依赖于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, …)删除可能由前导和尾随分隔符创建的空字符串(因为空字符串具有false布尔值)。

正如问题标题中所要求的那样,re.split()正好“分裂多个分隔符”。

此解决方案还可以免受其他一些解决方案中的非ASCII字符问题的影响(参见ghostdog74's answer的第一条评论)。

re模块比“手动”执行Python循环和测试更有效(速度和简洁)!

答案 4 :(得分:53)

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

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

答案 5 :(得分:38)

专业提示:使用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倍!

答案 6 :(得分:24)

有点迟到的回答:),但我有类似的困境,并且不想使用'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']

答案 7 :(得分: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]

然后这变成了三个班轮:

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

<强>解释

这就是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

这种方法的优点:

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

答案 8 :(得分:9)

首先,我想与其他人一致认为正则表达式或基于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.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()

答案 9 :(得分:4)

使用替换两次:

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

结果:

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

答案 10 :(得分:4)

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

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 .__包含__ 是'in'运算符使用的方法。基本上它与

相同
lambda ch: ch in sep

但这里更方便。

groupby 获取我们的字符串和函数。它使用该函数将字符串分组:只要函数值发生变化,就会生成一个新组。因此, sep .__包含__ 正是我们所需要的。

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

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

答案 11 :(得分:4)

您可以使用pandas的series.str.split方法来获得相同的结果,而不是使用re模块功能re.split。

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

my_list[-seq(1:2)] # $`c` # [1] 4 # $d # [1] "Hello" # $e # [1] NA

参数 pat 使用分隔符,并将拆分后的字符串作为数组返回。这里,两个定界符使用|传递。 (或运算符)。 输出如下:

thestring = pd.Series("Hey, you - what are you doing here!?") thestring.str.split(pat = ',|-')

答案 12 :(得分:4)

试试这个:

import re

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

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

答案 13 :(得分:3)

我正在重新认识Python,需要同样的东西。 findall解决方案可能会更好,但我想出了这个:

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

答案 14 :(得分:3)

在Python 3中,您可以使用PY4E - Python for Everybody中的方法。

  

我们可以使用字符串方法lowerpunctuationtranslate解决这两个问题。 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']

有关更多信息,请参阅:

答案 15 :(得分:2)

使用maketrans并翻译你可以轻松而整洁地完成

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

答案 16 :(得分:1)

实现此目的的另一种方法是使用自然语言工具包(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']

此方法的最大缺点是您需要install the nltk package

一旦获得令牌,您可以使用nltk软件包的其余部分a lot of fun stuff获益。

答案 17 :(得分:1)

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

我经常遇到这种情况,我通常的解决方案不需要重新使用。

单行lambda函数w / list comprehension:

(需要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或列表理解的常规功能

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

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

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

答案 18 :(得分:1)

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

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

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

答案 19 :(得分:1)

以下是一些解释的答案。

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

更新回答

答案 20 :(得分:1)

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

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

答案 21 :(得分:0)

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

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

答案 22 :(得分:0)

我最近需要执行此操作,但想要一个与标准库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])

注意:仅当分隔符由单个字符组成时(如我的用例一样),此功能才有用。

答案 23 :(得分:0)

我喜欢pprzemek的解决方案,因为它不假定定界符是单个字符,并且不尝试利用正则表达式(如果分隔符的数目太长了,它将不能很好地工作)。

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

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

答案 24 :(得分:0)

如果您想要可逆操作(保留分隔符),可以使用此功能:

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

答案 25 :(得分:0)

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

答案 26 :(得分:0)

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

以下是用法:

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

答案 27 :(得分:0)

继承我对它的看法......

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

答案 28 :(得分:0)

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

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

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

答案 29 :(得分:0)

以下是我与多个分发者的分歧:

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

答案 30 :(得分:0)

与@ooboo有同样的问题并找到这个主题 @ ghostdog74启发了我,也许有人发现我的解决方案很有用

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

如果您不想在空格处分割,请在空白处输入内容并使用相同的字符进行拆分。

答案 31 :(得分:-5)

您需要Python RegEx模块findall()方法:

http://www.regular-expressions.info/python.html

Example