在文本中使用字符串之前和之后获取单词的有效方法(python)

时间:2015-08-08 18:55:41

标签: python regex string

我正在使用正则表达式来查找文本正文中字符串模式的出现。一旦我发现字符串模式出现,我想在字符串之前和之后得到x个单词(x可以小到4,但如果仍然有效则最好是~10。)

我目前正在使用正则表达式查找所有实例,但偶尔会挂起。有没有更有效的方法来解决这个问题?

这是我目前的解决方案:

sub = r'(\w*)\W*(\w*)\W*(\w*)\W*(\w*)\W*(%s)\W*(\w*)\W*(\w*)\W*(\w*)\W*(\w*)' % result_string #refind string and get surrounding += 4 words
surrounding_text = re.findall(sub, text)
for found_text in surrounding_text:
  result_found.append(" ".join(map(str,found_text)))

4 个答案:

答案 0 :(得分:6)

我不确定这是否是您正在寻找的:

>>> text = "Hello, world. Regular expressions are not always the answer."
>>> words = text.partition("Regular expressions")
>>> words
('Hello, world. ', 'Regular expressions', ' are not always the answer.')
>>> words_before = words[0]
>>> words_before
'Hello, world. '
>>> separator = words[1]
>>> separator
'Regular expressions'
>>> words_after = words[2]
>>> words_after
' are not always the answer.'

基本上,str.partition()将字符串拆分为3元素元组。在这个例子中,第一个元素是特定"分隔符"之前的所有单词,第二个元素是分隔符,第三个元素是分隔符后的所有单词。

答案 1 :(得分:0)

制作一个正则表达式(好吧,任何东西,就此而言)“尽可能多的重复”是一个非常糟糕的主意。那是因为你

  • 每次都做过多的不必要的工作
  • 无法确定可能需要多少,从而引入任意限制

以下解决方案的底线:第一种解决方案是大数据最有效的解决方案;第二个是最接近你当前的,但是更糟糕。

  1. 在每个时刻将您的实体剥离到您感兴趣的内容:

    • 找到子字符串(例如str.index。仅对于整个字词,re.find例如r'\b%s\b'%re.escape(word)更合适)
    • 回N字。
      • 由于您提到了“文字”,因此您的字符串可能非常大,因此您希望避免复制可能无限制的字符串。
      • E.g。 re.finditer根据slices to immutable strings by reference and not copyBest way to loop over a python string backwards在子串 - 反向迭代器上进行。re。当后者在CPU和/或内存方面很昂贵时,这只会比切片更好 - 测试一些现实的例子来查找。不起作用。 (i for i,c in enumerate(reversed(buffer(text,0,substring_index)) if c.isspace())直接与内存缓冲区一起工作。因此,如果不复制数据,就不可能为它反转字符串。
      • 在Python nor an "xsplit"中找不到类中的字符是没有功能的。因此,最快的方式似乎是timeit\w*\W*在P3 933MHz上提供约100毫秒的完整传递100k字符串)。
  2. 可替换地:

    1. Fix your regex to not be subject to catastrophic backtracking并消除代码重复(DRY原则) 第二项措施将消除第二个问题:我们将明确重复的次数(Python Zen,koan 2),从而高度可见和易于管理。
      至于第一个问题,如果在每种情况下实际上只需要“达到已知的,相同的N”项目,那么通过与字符串一起找到它们实际上不会做“过度工作”。

      • 此处的“修复”部分为\w+\W+ - > x*。这消除了主要的歧义(参见上面的链接),因为每个(\w+\W+){,10}都可以是空白匹配。
      • 在字符串之前匹配最多N 字有效地更难:
        • \b或等效,匹配器会发现每个 10个单词,然后发现你的字符串不跟随它们,然后尝试9,8等等。在匹配器上,{,10}在模式之前将使它只在每个单词的开头执行所有这些工作
        • 此处不允许
        • lookbehind:正如链接文章所述,正则表达式引擎必须知道在尝试包含的正则表达式之前要退回多少个字符。即使它是 - 在每个字符之前尝试一个lookbehind - 即它更像是一个CPU hog
        • 正如你所看到的那样,正则表达式并没有完全削减以匹配事物
      • 要消除代码重复
        • 使用上述(\w+\W+)?。这不会保存单个单词,但对于大文本应该明显更快(请参阅上面的匹配如何工作)。我们可以随时解析检索到的大量文本(在下一个项目中使用正则表达式)。或
        • 自动生成重复部分
          • 请注意,w=(\w+\W+)无意识地重复出现与上述相同的歧义。为了明确,表达式必须像这样((w(w...(ww?)?...)?)?为了简洁起见):var parsed = [{ "id" : "33", "first_name" : "Oleg", "last_name" : "Smith" }]; var aaData = []; for(key in parsed[0]) { aaData.push(parsed[0][key]); } (并且所有组都需要非捕获)。

答案 2 :(得分:0)

你的模式的主要问题是它以可选的东西开始,导致对字符串中的每个位置进行大量尝试,直到找到匹配为止。尝试次数随文本大小和n值(前后字数)而增加。这就是为什么只有几行文字就足以使您的代码崩溃。

一种方法是使用目标词开始模式并使用外观来捕获前后的文本(或单词):

keyword (?= words after ) (?<= words before - keyword)

使用搜索到的单词(文字字符串)启动模式会使其非常快,然后可以从字符串中的此位置快速找到周围的单词。不幸的是,re模块有一些限制,并且不允许可变长度的外观(与许多其他正则表达式一样)。

新的regex module支持可变长度的外观和其他有用的功能,例如存储重复捕获组匹配的功能(方便一次获取分离的单词)。

import regex

text = '''In strange contrast to the hardly tolerable constraint and nameless
invisible domineerings of the captain's table, was the entire care-free
license and ease, the almost frantic democracy of those inferior fellows
the harpooneers. While their masters, the mates, seemed afraid of the
sound of the hinges of their own jaws, the harpooneers chewed their food
with such a relish that there was a report to it.'''

word = 'harpooneers'
n = 4

pattern = r'''
\m (?<target> %s ) \M # target word
(?<= # content before
    (?<before> (?: (?<wdb>\w+) \W+ ){0,%d} )
    %s
)
(?=  # content after
    (?<after>  (?: \W+ (?<wda>\w+) ){0,%d} )
)
''' % (word, n, word, n)

rgx = regex.compile(pattern, regex.VERBOSE | regex.IGNORECASE)

class Result(object):
    def __init__(self, m):
        self.target_span = m.span()
        self.excerpt_span = (m.starts('before')[0], m.ends('after')[0])
        self.excerpt = m.expandf('{before}{target}{after}')
        self.words_before = m.captures('wdb')[::-1]
        self.words_after = m.captures('wda')


results = [Result(m) for m in rgx.finditer(text)]

print(results[0].excerpt)
print(results[0].excerpt_span)
print(results[0].words_before)
print(results[0].words_after)
print(results[1].excerpt)

答案 3 :(得分:0)

我个人认为使用text.partition()是最好的选择,因为它消除了混乱的正则表达式,并自动将输出保留在易于访问的元组中。