【问题标题】:Find Pattern in Textfile From Several Elements In Several Lists?从多个列表中的多个元素中查找文本文件中的模式?
【发布时间】:2013-09-27 05:01:11
【问题描述】:

我是一个初学者,几个月来一直在学习 Python 作为我的第一门编程语言。我正在寻找从文本文件中查找模式。我的第一次尝试是使用正则表达式,它确实有效但有一个限制:

import re

noun_list = ['bacon', 'cheese', 'eggs', 'milk', 'list', 'dog']
CC_list = ['and', 'or']

noun_list_pattern1 = r'\b\w+\b,\s\b\w+\b,\sand\s\b\w+\b|\b\w+\b,\s\b\w+\b,\sor\s\b\w+\b|\b\w+\b,\s\b\w+\b\sand\s\b\w+\b|\b\w+\b,\s\b\w+\b,\saor\s\b\w+\b'

with open('test_sentence.txt', 'r') as input_f:
    read_input = input_f.read()
    word = re.findall(noun_list_pattern1, read_input)
    for w in word:
        print w
else:
    pass

所以此时您可能会问,为什么在这段代码中的列表没有被使用。好吧,我一直在绞尽脑汁,尝试各种 for 循环和函数中的 if 语句,试图找出复制正则表达式模式的原因,但使用列表。

正则表达式的局限性在于,在 `noun_list_pattern' 中多次找到的 \b\w+\w\ 代码实际上只能找到单词 - 任何单词 - 而不是特定名词。这可能会引发误报。我想通过使用上面列表中的元素而不是正则表达式来进一步缩小范围。

因为我在正则表达式模式中实际上有 4 个不同的正则表达式(它包含 4 个|),所以我将只选择其中的一个。所以我需要找到一个模式,比如:

'noun in noun_list' + ', ' + 'noun in noun_list' + ', ' + 'C in CC_list' + ' ' + 'noun in noun_list

显然,上面引用的代码行不是真正的python代码,而是我对所需匹配的想法的表达。我说noun in noun_list 的意思是通过名词列表的迭代; C in CC_list 是通过 CC_list 的迭代; , 是逗号和空格的文字字符串匹配。

希望我已经说清楚了!

这是我正在使用的test_sentence.txt 文件的内容:

I need to buy are bacon, cheese and eggs. 
I also need to buy milk, cheese, and bacon.
What's your favorite: milk, cheese or eggs.
What's my favorite: milk, bacon, or eggs.

【问题讨论】:

  • 你能发一个你想匹配的数据的例子吗?
  • 哎呀!是的,会的,忘记了。
  • 您是否尝试分析每个句子的结构并将相似的内容归为一类?如果是,您可能想尝试 nltk 库:nltk.org 他们还有一个写得很好的免费文档(作为一本书提供)作为初学者指南。
  • @Mai 我确实花了一些时间在 nltk;但是,我发现它们并不完美,也不能正确地将名词标记为名词。所以,我决定使用我自己的名词列表。不过,nltk 发生的事情真是太神奇了。

标签: python regex list


【解决方案1】:

稍微分解一下你的问题。首先,您需要一个模式来匹配列表中的单词,但不能匹配其他单词。您可以使用交替运算符 | 和文字来完成此操作。例如,red|green|blue 将匹配 "red""green""blue",但不匹配 "purple"。用该字符加入名词列表,并添加单词边界元字符和括号以对交替进行分组:

noun_patt = r'\b(' + '|'.join(nouns) + r')\b'

对你的连词列表做同样的事情:

conj_patt = r'\b(' + '|'.join(conjunctions) + r')\b'

您要进行的总体匹配是“一个或多个noun_patt 匹配,每个可选后跟一个逗号,后跟一个conj_patt 匹配,然后再一个noun_patt 匹配”。正则表达式足够简单:

patt = r'({0},? )+{1} {0}'.format(noun_patt, conj_patt)

您并不是真的想使用re.findall(),而是re.search(),因为您只希望每行有一个匹配项:

for line in lines:
...     print re.search(patt, line).group(0)
... 
bacon, cheese and eggs
milk, cheese, and bacon
milk, cheese or eggs
milk, bacon, or eggs

请注意,就解析英语而言,您已经接近(如果不是碰到)正则表达式的限制。比这更复杂的,你会想要研究实际的解析,也许是使用 NLTK。

【讨论】:

  • 谢谢乔希 =) 那是完美的。作为一个新手,我不知道.format 方法,非常有用。我还从您为 noun_patt 和 conj_patt 列表格式化正则表达式的方式中学到了一些巧妙的东西。因为根据我读取文件的方式,每行(或整个文件)可能需要更多一个匹配项,所以我可以使用re.findall() 和 `re.search()' 来获得我需要的任何给定的内容情况。这种正则表达式方法对于我需要的其他类似东西非常有用。再次感谢 =)
  • Josh,我还想知道您是否介意向我解释一下noun_pattconj_patt 中括号的位置?我尝试查看文档,但无法弄清楚。我的理解是正则表达式中的括号只返回了匹配的那部分,那么代码是否没有返回括号外的\b?另外我很好奇括号是如何定位在两个单引号之间的,例如noun_patt 列表的r'\b('')\b' 段。这就像单引号之间的括号“挂钩”
  • 1 more thing =) 我尝试了这个脚本,方法是使用.read() 方法打开文本文件,使用re.findall() 函数后跟for 循环打印每个“查找”,如果@ 987654346@。结果有点偏离,但很接近。出于某种原因,我们得到了一个重复的名词,即第一行:('cheese ', 'cheese', 'and', 'eggs')。我们得到 2 种“奶酪”的变体——带和不带尾随空格。其他印刷线跟随在同一套装中。不知道为什么使用.read() 方法会改变正则表达式的匹配方式?我玩了一点正则表达式,没有得到想要的结果。
  • @Darren,括号是为了将单词的交替与合并到最终模式中时可能出现在模式旁边的任何其他内容隔离开来。管道的绑定优先级最低,因此如果将milk|cheese 插入到所示的模式中,则匹配只会对"milk""cheese," 成功,而不是"milk,"。与 C 不同,单引号和双引号在 Python 中是完全等价的——两者都创建相同类型的字符串。所以这些操作只是前置和附加由单词边界字符和括号组成的字符串。
  • @Darren,因为findall() 不会返回整个匹配项,它会返回一个包含各种匹配项 的元组。第一组是(noun_patt,? ),匹配"cheese "。第二组位于noun_patt 本身,与"cheese" 匹配。如果要将文件作为单个字符串进行扫描,则应使用 finditer()
【解决方案2】:

实际上,您不一定需要正则表达式,因为有多种方法可以仅使用您的原始列表。

noun_list = ['bacon', 'cheese', 'eggs', 'milk', 'list', 'dog']
conjunctions = ['and', 'or']

#This assumes that file has been read into a list of newline delimited lines called `rawlines`
for line in rawlines:
    matches = [noun for noun in noun_list if noun in line] + [conj for conj in conjunctions if conj in line]
    if len(matches) == 4:
        for match in matches:
            print match

匹配数是 4 的原因是 4 是正确的匹配数。 (注意,这也可能是重复名词或连词的情况)。

编辑:

此版本打印匹配的行和匹配的单词。还修复了可能的多词匹配问题:

words_matched = []
matching_lines = []

for l in lst:
    matches = [noun for noun in noun_list if noun in l] + [conj for conj in conjunctions if conj in l]
    invalid = True
    valid_count = 0
    for match in matches:
        if matches.count(match) == 1:
            valid_count += 1
    if valid_count == len(matches):
        invalid = False

    if not invalid:
        words_matched.append(matches)
        matching_lines.append(l)

for line, matches in zip(matching_lines, words_matched):
    print line, matches

但是,如果这不适合您,您始终可以按如下方式构建正则表达式(使用 itertools 模块):

#The number of permutations choices is 3 (as revealed from your examples)
for nouns, conj in itertools.product(itertools.permutations(noun_list, 3), conjunctions):
    matches = [noun for noun in nouns]
    matches.append(conj)
    #matches[:2] is the sublist containing the first 2 items, -1 is the last element, and matches[2:-1] is the element before the last element (if the number of nouns were more than 3, this would be the elements between the 2nd and last).
    regex_string = '\s,\s'.join(matches[:2]) + '\s' + matches[-1] + '\s' + '\s,\s'.join(matches[2:-1])
    print regex_string
    #... do regex related matching here

此方法的注意事项是它是纯粹的暴力破解,因为它会生成两个列表的所有可能组合(读取排列),然后可以对其进行测试以查看每行是否匹配。因此,它非常慢,但在这个匹配给定的例子(连词之前的非逗号)的例子中,这将完美地生成完全匹配

根据需要进行调整。

【讨论】:

  • 感谢您提供2个详细的答案,感谢您的帮助。我刚刚尝试了您的第一个代码,即不使用正则表达式的代码,它正在打印:baconcheeseeggsandbaconcheesemilkandcheese@9876 @milkorbaconeggsmilkor这些单词中的每一个都打印在一个新的行上。我正在寻找的是 bacon, cheese, and eggs 在第一行作为第一场比赛,然后在下一行 milk, cheese, and bacon 等等。
  • (另外,有趣的是,您使用 itertools 排列函数。我将其与该脚本的长期目标挂钩,在该脚本中我创建所有排列并使用它们来替换匹配项。但这是另一个天!)
  • @Darren:这就是我实现它的方式,正如我从您的原始问题中解释的那样。您引用的输出是在nouns_listconjunctions 的每一行中找到的单词。那主要是为了验证输出。我使用的逻辑是,如果这些单词在行中(编号为 4),那么这就是匹配 - 这就是您想要的,对吗?或者,不幸的是我误解了你的问题?
  • @Darren:实际上,我们的两个示例都匹配所有单词。 Josh 的解决方案和我的一样,会根据单词生成一个正则表达式。但是,我已将我的解决方案编辑为(如果我错了,请纠正我),您想要匹配的行,而不是匹配的单词 - 正确吗?
  • @Darren:啊!我知道了。那么你毕竟需要regexes。在这种情况下,乔希的答案就是要走的路。 :)
猜你喜欢
  • 2023-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-14
  • 2011-07-29
  • 2018-12-14
  • 1970-01-01
  • 2013-11-14
相关资源
最近更新 更多