【问题标题】:How to make exceptions for certain words in regex如何对正则表达式中的某些单词进行例外处理
【发布时间】:2016-11-19 08:22:48
【问题描述】:

我是编程和正则表达式的新手,如果以前有人问过这个问题,我深表歉意(不过我没有找到)。

我想使用 Python 来总结文字文本中的词频。让我们假设文本的格式类似于

Chapter 1
blah blah blah

Chapter 2
blah blah blah
....

现在我将文本读取为字符串,并且我想使用re.findall 来获取此文本中的每个单词,所以我的代码是

wordlist = re.findall(r'\b\w+\b', text)

但问题是它与每个章节标题中的所有这些Chapters 匹配,我不想将其包含在我的统计数据中。所以我想忽略匹配Chapter\s*\d+ 的内容。我该怎么办?

提前谢谢各位。

【问题讨论】:

  • 试试r'\b(?!Chapter\s*\d)\w+\b'
  • 使用re.findall(r'\bChapter\s*\d+\b|\b(\w+)\b',s)
  • 使用简单的正则表达式,然后删除不需要的。
  • @BryanOakley 也许在大多数情况下我会这样做,毕竟我更喜欢处理字符串而不是摆弄正则表达式,除非,例如,有 很多 我想在正则表达式查询中创建异常(比如我想忽略第 X 章、第 X 节、第 X 小节等),然后删除它们可能会很麻烦。

标签: python regex regex-negation


【解决方案1】:

解决方案

您可以先删除所有Chapter+space+digits

wordlist = re.findall(r'\b\w+\b', re.sub(r'Chapter\s*\d+\s*','',text))

如果您只想使用一次搜索,您可以使用否定前瞻来查找前面没有“Chapter X”且不以数字开头的任何单词:

wordlist = re.findall(r'\b(?!Chapter\s+\d+)[A-Za-z]\w*\b',text)

如果性能是一个问题,加载一个巨大的字符串并使用正则表达式解析它无论如何都不是正确的方法。只需逐行读取文件,丢弃任何与r'^Chapter\s*\d+' 匹配的行,然后用r'\b\w+\b' 分别解析剩余的每一行:

import re

lines=open("huge_file.txt", "r").readlines()

wordlist = []
chapter = re.compile(r'^Chapter\s*\d+')
words = re.compile(r'\b\w+\b')
for line in lines:
  if not chapter.match(line):
    wordlist.extend(words.findall(line))

print len(wordlist)

性能

我写了一个小的 ruby​​ 脚本来写一个大文件:

all_dicts = Dir["/usr/share/dict/*"].map{|dict|
  File.readlines(dict)
}.flatten

File.open('huge_file.txt','w+') do |txt|
  newline=true
  txt.puts "Chapter #{rand(1000)}"
  50_000_000.times do
    if rand<0.05
      txt.puts
      txt.puts
      txt.puts "Chapter #{rand(1000)}"
      newline = true
    end
    txt.write " " unless newline
    newline = false
    txt.write all_dicts.sample.chomp
    if rand<0.10
      txt.puts
      newline = true
    end
  end
end

生成的文件有超过 5000 万字,大小约为 483MB:

Chapter 154
schoolyard trashcan's holly's continuations

Chapter 814
assure sect's Trippe's bisexuality inexperience
Dumbledore's cafeteria's rubdown hamlet Xi'an guillotine tract concave afflicts amenity hurriedly whistled
Carranza
loudest cloudburst's

Chapter 142
spender's
vests
Ladoga

Chapter 896
petition's Vijayawada Lila faucets
addendum Monticello swiftness's plunder's outrage Lenny tractor figure astrakhan etiology's
coffeehouse erroneously Max platinum's catbird succumbed nonetheless Nissan Yankees solicitor turmeric's regenerate foulness firefight
spyglass
disembarkation athletics drumsticks Dewey's clematises tightness tepid kaleidoscope Sadducee Cheerios's

两步过程提取词表平均需要 12.2 秒,前瞻方法需要 13.5 秒,而 Wiktor 的回答也需要 13.5 秒。我最开始写的lookahead方法使用了re.IGNORECASE,耗时18s左右。

读取整个文件时,所有Regexen方法的性能基本没有差异。

但令我惊讶的是,readlines 脚本花费了大约 20.5 秒,并且使用的内存并没有比其他脚本少多少。如果您有任何想法如何改进脚本,请发表评论!

【讨论】:

  • 这似乎是解决手头问题的更简单方法
  • 谢谢。这种方法很干净,但是这里的权衡是整个文本(几乎)必须搜索两次。
  • 是的。不过有几点: 1. 如果您不搜索千兆字节值或文本,您可能不会注意到任何差异 2. 此代码更易于编写、阅读、理解和调试。这比在执行期间少几毫秒的价值要多得多。 3. 具有环视、组或布尔逻辑的复杂正则表达式可能比 2 个单独的正则表达式慢得多。您可以使用regex101.com 查看步数。我的查询需要 22+57 步。另一个答案需要 154。
  • Eric,你忘了 regex101.com 的步数并不代表真正的正则表达式性能。 应该在目标环境中计算真正的正则表达式效率。 现在,如果您需要排除更多短语怎么办?使用我的方法,只需添加替代方案而不捕获。您将需要在 re.sub 中使用相同的分组,这将使正则表达式的性能与我的单个分组相当,或者使用链接的 re.subs。
  • 我用 2 个备选方案更新了答案,具体取决于对您而言更重要的是什么。
【解决方案2】:

匹配您不需要的并捕获您需要的,并将此技术与仅返回捕获值的re.findall 一起使用:

re.findall(r'\bChapter\s*\d+\b|\b(\w+)\b',s)

详情:

  • \bChapter\s*\d+\b - Chapter 是一个完整的单词,后跟 0+ 个空格和 1+ 个数字
  • | - 或
  • \b(\w+)\b - 将一个或多个单词字符匹配并捕获到第 1 组中

为避免在结果列表中出现空值,请对其进行过滤(请参阅demo):

import re
s = "Chapter 1: Black brown fox 45"
print(filter(None, re.findall(r'\bChapter\s*\d+\b|\b(\w+)\b',s)))

【讨论】:

  • 谢谢。但是代码仍然返回一些空字符串'' 以及列表中的单词。另外,为什么第一部分需要\bs?
  • 这种正则表达式方法对我来说似乎是最干净的。我没有时间也没有办法测试解决方案的性能,尽管如果这是一个问题,正则表达式可以写成r'\b(?:Chapter\s*\d+|(\w+))\b。永远不要盲目相信 regex101 步数。如果您真的想测试我们的解决方案,请使用 Python 进行测试。如果你可以使用 PyPi 正则表达式模块,我可以建议一个类似的正则表达式,而不必使用 filter
猜你喜欢
  • 2019-03-20
  • 2021-09-27
  • 2011-09-28
  • 1970-01-01
  • 2013-06-16
  • 2011-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多