根据有趣单词词典从电子邮件列表中识别重复出现的单词

时间:2012-06-11 18:11:23

标签: python regex bash parallel-processing grep

目录D包含.eml格式的几千封电子邮件。一些电子邮件是纯文本,其他电子邮件来自Outlook,其他电子邮件具有ASCII标头和HTML / MIME内容等等。存在一个字典文件F,其中包含要在D目录下面的文件中查找的有趣单词列表(即red \ nblue \ ngreen \ n ...)。 D目录有大量子文件夹,但没有上述.eml文件以外的文件。应使用这些规范列出重复出现的单词:

  • 对于每个有趣的单词,应提供有关它出现次数和出现次数的信息。如果它在文件中多次出现,则应该多次报告该文件。报告发生意味着报告整数的元组(L,P),其中L是电子邮件源顶部的行号,P是该行中起始点的位置。

这将构建一个引用不同事件的索引和最常出现的有趣词汇的摘要。

输出应该在单个输出文件上,并且格式没有严格定义,只要包括上面的信息:有趣的单词,每个有趣单词出现的次数以及它的作用 - >文件/行/启动位置。

这不是一个家庭作业练习,而是我想要制作一个相当大的数据集的实际文本分析。我遇到的挑战是选择合适的过滤工具进行有效过滤。一个迭代的方法,单词/电子邮件/等的笛卡尔积,太慢了,并且希望为每个文件的每一行组合多个单词过滤。

我已经尝试从有趣的单词列表w1 | w2 | w3 | ...构建替代正则表达式,编译并运行每个电子邮件的每一行,但它仍然很慢,特别是当我需要时在一行内检查多次出现。

示例:

电子邮件E有一行包含文字:

^ ......等等......红苹果......蓝色蓝莓......红色,白色和蓝色标志。$ \ n

正则表达式正确地报告了红色(2)和蓝色(2),但是当使用真实的,非常大的有趣单词词典时它很慢。

我尝试的另一种方法是:

使用Sqlite数据库在解析时转储令牌,包括每个条目的(列,位置)信息,并在最后查询输出。批量插入对于使用适当的内存缓冲区有很大帮助,但会增加复杂性。

我还没有尝试过数据并行化,因为我不确定令牌/解析是否是首先要做的事情。也许一棵树更合适?

我对解决方案感兴趣,按优先顺序排列:

  • Bash / GNU CLI工具(特别是通过GNU'并行'可并行执行的CLI)
  • Python(NLP?)
  • C / C ++
不幸的是,没有Perl,因为我不理解。

3 个答案:

答案 0 :(得分:2)

一些评论:

  • 我们不希望为“所有电子邮件,执行正则表达式搜索和do_something();”这样做。我可以想象大多数电子邮件的长度都比有趣的单词列表短,所以我会尝试单独处理每封电子邮件并提取必要的信息。
  • 构建专门的字符串数据结构(例如string trieternary search tree)以快速查找单词是否有趣。我有很好的构建三元搜索词的经验,因为它可以快速查找单词。
  • 算法将如下所示:

(当然是伪代码)

result <- empty list
for each email e:
    for each word w:
        if is_interesting_word(w, string_data_structure):
            add (filename, line_number, start_position, word) to results
  • 此问题现在非常适合与MapReduce(例如Hadoop)等技术并行化。每个电子邮件都可以独立于其他电子邮件进行处理,不需要共享任何信息:可以在处理电子邮件之前计算字符串数据结构。在地图步骤中,您从电子邮件中提取必要的信息,然后在缩减步骤中,将每封电子邮件中的计算值合并到一个输出文件中。

我会减少你需要的处理量:没有正则表达式,没有高级解析;只需遍历电子邮件中的每个字符/行,并跟踪您的位置(行号,位置等)。最后一步,分析您的代码并优化它所受的伤害:)

答案 1 :(得分:2)

我假设你可以创建/找到一个eml-to-text转换器。那么这与你想要的非常接近:

find -type f | parallel --tag 'eml-to-text {} | grep -o -n -b -f /tmp/list_of_interesting_words'

输出格式不是100%格式化:

filename \ t line no:byte no(从文件开头):word

如果你有很多有趣的词在grep是慢启动,因此,如果您可以创建邮件目录的解压版本,你可以使并行启动grep次数越少“-f”:

find . -type f | parallel 'eml-to-text {} >/tmp/unpacked/{#}'
find /tmp/unpacked -type f | parallel -X grep -H -o -n -b -f /tmp/list_of_interesting_words

由于grep -f的时间复杂度比线性更差,您可能希望将/ tmp / list_of_interesting_words切割成更小的块:

cat /tmp/list_of_interesting_words | parallel --pipe --block 10k --files > /tmp/blocks_of_words

然后并行处理块和文件:

find /tmp/unpacked -type f | parallel -j1 -I ,, parallel --arg-file-sep // -X grep -H -o -n -b -f ,, {} // - :::: /tmp/blocks_of_words

此输出的格式如下:

filename:行号:字节号(从文件开头):单词

要按word分组,而不是文件名,请通过sort:

来输出结果
... | sort -k4 -t: > index.by.word

计算频率:

... | sort -k4 -t: | tee index.by.word | awk 'FS=":" {print $4}' | uniq -c

好消息是,这应该是相当快的,我怀疑你将能够使用Python达到相同的速度。

编辑:

grep -F在启动时速度更快,你需要-w用于grep(因此'gram'这个词与'diagram'不匹配);这也将避免临时文件,并且可能相当快:

find . -type f | parallel --tag 'eml-to-text {} | grep -F -w -o -n -b -f /tmp/list_of_interesting_words' | sort -k3 -t: | tee index.by.word | awk 'FS=":" {print $3}' | uniq -c

答案 2 :(得分:0)

的Python:

list = ['a', 'bunch', 'of', 'interesting', 'words']
linepos = 0

with open("file") as f:
    for line in f:
        linepos += 1
        wordpos = 0
        for word in line.split():
            wordpos += 1
            if word in list:
                print "%s found at line %s, word %s" % (word, linepos, wordpos)