regex 有效的方法来获取文本中子字符串之前和之后的单词(python)

wlzqhblo  于 12个月前  发布在  Python
关注(0)|答案(4)|浏览(126)

我使用正则表达式来查找文本中出现的字符串模式。一旦我发现字符串模式出现,我想在字符串之前和之后得到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)))
oaxa6hgo

oaxa6hgo1#

我不确定这是不是你要找的:

>>> 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元素元组。在这个例子中,第一个元素是特定的“分隔符”之前的所有单词,第二个元素是分隔符,第三个元素是分隔符之后的所有单词。

yrdbyhpb

yrdbyhpb2#

你的模式的主要问题是,它以可选的东西开始,这会导致对字符串中每个位置进行多次尝试,直到找到匹配。尝试的次数随着文本大小和n值(前后的单词数)的增加而增加。这就是为什么只有几行文本就足以使代码崩溃的原因。
一种方法是从目标词开始开始模式,并使用lookarounds来捕捉前后的文本(或单词):

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

用搜索到的单词(一个文字字符串)开始一个模式,这使得它非常快(因为一个快速算法被用来在字符串中找到这个文字字符串的位置,然后只在这些位置测试模式),然后从字符串中的这个位置快速找到周围的单词。不幸的是,re模块有一些限制,不允许可变长度的lookbehind(和其他正则表达式一样)。
新的regex module支持可变长度的lookbehinds和其他有用的功能,例如存储重复捕获组的匹配的能力(方便一次获得分离的单词)。

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'''
 %s  # target word
(?<= # content before
    (?<before> (?: (?<wdb>\w+) \W+ ){0,%d} )
    \m (?<target> %s ) \M
)
(?=  # 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)
4bbkushb

4bbkushb3#

使用“尽可能多的重复”来创建一个正则表达式(好吧,任何东西,就此而言)是一个非常糟糕的主意。

  • 每次都做过多的不必要的工作
  • 我不能确切地知道你将 * 可能 * 需要多少,因此引入了一个任意的限制

以下解决方案的底线:对于大数据,第一种解决方案是最有效的解决方案;第二个是最接近你现在的,但规模更差。

  • 把你的实体剥离到 * 你在每一个时刻都感兴趣的东西:*
  • 查找子串(例如,str.index。仅适用于整个单词,re.find与例如r'\b%s\b'%re.escape(word)更合适)
  • 回N个字。
  • 由于您提到了一个“文本”,因此字符串可能非常大,因此您希望避免可能无限制地复制它们。
  • 例如,re.finditer在一个子串上根据slices to immutable strings by reference and not copyBest way to loop over a python string backwards进行反向迭代。这只会变得比切片更好,当后者在CPU和/或内存方面是昂贵的-测试一些现实的例子来找出。坏了re直接使用内存缓冲区。因此不可能在不复制数据的情况下为它反转字符串。
  • 在Python中没有从类nor an "xsplit"中查找字符的函数。因此,最快的方法似乎是(i for i,c in enumerate(reversed(buffer(text,0,substring_index)) if c.isspace())timeit在P3 933 MHz上给出约100 ms,以完全通过100 k字符串)。

或者:

  • 修复正则表达式,使其不会受到灾难性回溯的影响,并消除代码重复(DRY原则)。

第二项措施将消除第二个问题:我们将使重复的次数明确(Python Zen,koan 2),从而高度可见和可管理。
至于第一个问题,如果你 * 真的只需要“最多已知的,相同的N”* 项在每种情况下,你实际上不会做“多余的工作”,找到他们与你的字符串。

  • 这里的“修复”部分是\w*\W*-> \w+\W+。这消除了每个x*都可以是空白匹配这一事实的主要歧义(参见上面的链接)。
  • 有效地匹配字符串之前的 * 最多N* 个单词更难:
  • 使用(\w+\W+){,10}或等效的,匹配器将在发现你的字符串不跟随它们之前,每10个单词查找一次,然后尝试9,8等。为了在某种程度上减轻匹配器的负担,模式之前的\b将使它只在每个单词的开头执行所有这些工作
  • lookbehind在这里不允许:正如链接的文章所解释的那样,正则表达式引擎必须知道在尝试包含的正则表达式之前要后退多少个字符。即使它是-一个向后看是尝试之前 * 每 * 字符-即。它甚至更占用CPU
  • 正如您所看到的,正则表达式并没有完全被裁剪以向后匹配
  • 为了消除代码重复,
  • 使用上述{,10}。这不会保存单个单词,但对于大文本应该会明显更快(请参阅上面关于匹配如何工作的内容)。一旦我们得到了文本块,我们总是可以更详细地解析检索到的文本块(下一项中有正则表达式)。或
  • 自动生成重复零件
  • 注意,无意识地重复的(\w+\W+)?受到与上述相同的歧义。为了明确起见,表达式必须像这样(为了简洁起见,这里是w=(\w+\W+)):(w(w...(ww?)?...)?)?(并且所有组都需要是非捕获的)。
ygya80vv

ygya80vv4#

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

相关问题