在Python中逐个单词遍历字符串

3

我有一个字符串缓冲区,存储着一个巨大的文本文件。我需要在这个字符串缓冲区里查找指定的单词/短语。有什么高效的方法可以做到这一点呢?

我尝试使用re模块匹配,但是由于我需要搜索的文本语料库非常庞大,所以这需要花费大量的时间。

给定一个单词和短语的字典。

我遍历每个文件,将其读入字符串,搜索字典中的所有单词和短语,并在字典中增加计数,如果找到了键。

我们想到的一个小优化是将短语/单词的字典按照单词数量最多到最少进行排序。然后从字符串缓冲区比较每个单词的起始位置并比较单词列表。如果找到一个短语,则不继续搜索其他短语(因为它匹配了最长的短语,这正是我们想要的)。

是否有人能够建议如何逐个单词地遍历字符串缓冲区呢?

此外,还能对此进行其他优化吗?

data = str(file_content)
for j in dictionary_entity.keys():
    cnt = data.count(j+" ")
    if cnt != -1:
        dictionary_entity[j] = dictionary_entity[j] + cnt
f.close()

我有一个庞大的文本语料库,正在尝试获取其中一组200万个短语/单词在该语料库中出现的次数。 - AlgoMan
你是在实现一个单词/短语计数器吗? - dlamotte
实现一个单词/短语计数器。语料库是我要搜索的字符串缓冲区。 有数百万个文件,我必须获取所有单词/短语(这是预定义的)出现的次数。 - AlgoMan
如果我的哈希词/短语列表中有"黄金之城"、"城市"和"黄金",并且在字符串缓冲区中有"This is City of Gold",那么我的计数器应该只增加"黄金之城"的数量。 - AlgoMan
8个回答

7

通过三种不同的方式逐字逐句地遍历文件内容(以我的情况为例,是从Project Gutenberg获取的《绿野仙踪》):

from __future__ import with_statement
import time
import re
from cStringIO import StringIO

def word_iter_std(filename):
    start = time.time()
    with open(filename) as f:
        for line in f:
            for word in line.split():
                yield word
    print 'iter_std took %0.6f seconds' % (time.time() - start)

def word_iter_re(filename):
    start = time.time()
    with open(filename) as f:
        txt = f.read()
    for word in re.finditer('\w+', txt):
        yield word
    print 'iter_re took %0.6f seconds' % (time.time() - start)

def word_iter_stringio(filename):
    start = time.time()
    with open(filename) as f:
        io = StringIO(f.read())
    for line in io:
        for word in line.split():
            yield word
    print 'iter_io took %0.6f seconds' % (time.time() - start)

woo = '/tmp/woo.txt'

for word in word_iter_std(woo): pass
for word in word_iter_re(woo): pass
for word in word_iter_stringio(woo): pass

导致:
% python /tmp/junk.py
iter_std took 0.016321 seconds
iter_re took 0.028345 seconds
iter_io took 0.016230 seconds

1
这个问题听起来像是需要一个字典树的地方,你可能需要使用一些压缩字典树,比如帕特里夏/基数树。只要你能把所寻找的单词/短语整个放入字典树中,它将大大降低时间复杂度。它的工作原理是你取单词的开头并沿着字典树向下查找,直到找到最长的匹配项并在该节点中递增计数器。这可能意味着如果部分匹配不成功,你需要向上走字典树。然后,你将继续到下一个单词的开头并再次执行此操作。字典树的优点是每次通过字典树搜索就可以搜索整个字典(每次查找应该大约花费O(m),其中m是字典中单词/短语的平均长度)。
如果你无法将整个字典放入一个字典树中,那么你可以将字典拆分成几个字典树(例如,一个用于以a-l开头的所有单词/短语,另一个用于以m-z开头的所有单词/短语),并对每个字典树进行全文扫描。

我有一个单词列表,大小为50MB。我需要搜索200万个单词/短语。 - AlgoMan
我刚刚用一个非常简单的 Patricia Trie 实现进行了一项测试,使用了 2 百万个平均长度为 22.5 个字母的随机生成短语。在我的计算机上,它占用了 684 MB 的空间。我还将这些随机生成的短语保存到了一个文本文件中,该文件大小为 48 MB。考虑到我的实现并不是非常内存高效,这似乎并不太糟糕。 - Justin Peel

0

您可以尝试换一种方式...而不是对文本语料库进行2,000,000次处理(每个单词一次),仅处理一次。对于语料库中的每个单词,在哈希表或类似结构中递增以存储该单词的计数。以下是一个简单的伪代码示例:

word_counts = new hash<string,int>
for each word in corpus:
  if exists(word_counts[word]):
    word_counts[word]++
  else:
    word_counts[word] = 1

你可以通过提前使用完整的单词列表初始化 word_counts 来加快速度,这样就不需要那个 if 语句了...不确定。


但是哈希表中的字符串可能包含多个单词。因此,逐个比较每个单词将为我提供“城市”和“黄金”的计数,但不会为“黄金之城”的计数。 - AlgoMan
@AlgoMan,你完全可以对每个单词或短语进行循环,并将两者都放入字典中。 - mikerobi
@mikerobi 我已经能够将短语放入字典中。但是,语料库是逐个单词搜索,而不是逐个短语搜索。我该如何通过短语搜索语料库并在单词上增加,然后再次搜索短语? - AlgoMan

0
如果使用re效率不够高,你可能在使用findall()或手动逐个查找匹配项。使用迭代器可能会使其更快:
>>> for i in re.finditer(r'\w+', 'Hello, this is a sentence.'):
...     print i.group(0)
...     
Hello
this
is
a
sentence

0
#!/usr/bin/env python
import re

s = ''
for i in xrange(0, 100000):
    s = s + 'Hello, this is a sentence. '
    if i == 50000:
        s = s + " my phrase "

s = s + 'AARRGH'

print len(s)

itr = re.compile(r'(my phrase)|(\w+)').finditer(s)
for w in itr:
    if w.group(0) == 'AARRGH':
        print 'Found AARRGH'
    elif w.group(0) == "my phrase":
        print 'Found "my phrase"'

运行此代码,我们得到:

$ time python itrword.py
2700017
Found "my phrase"
Found AARRGH

real    0m0.616s
user    0m0.573s
sys     0m0.033s

但是,每个显式添加到正则表达式中的“短语”都会对性能产生影响——根据我的粗略测量,上述方法比仅使用“\w+”慢50%。


但如果我想搜索一个短语呢? 如果w.group(0) == 'this is a': print "found 'this is a'" 我该如何使其工作? - AlgoMan
@AlgoMan:我认为中心问题是,“有人能建议如何逐字在字符串缓冲区中进行吗?(逐字迭代字符串缓冲区)?” 鉴于此,您将不得不在“for w in itr:”循环内部添加一些状态机或类似内容以逐字匹配短语。否则,需要比“\w+”更复杂的正则表达式。 - Kevin Little

0
如果re模块无法快速完成它,那么你很难以更快的速度完成它。无论如何,您需要阅读整个文件。您可以考虑修复您的正则表达式(您能提供一个吗?)。也许还需要一些关于您试图实现什么的背景信息。

0

正如xyld所说,我认为你无法超越re模块的速度,尽管如果您发布您的正则表达式和可能的代码将会有所帮助。我能添加的是在优化之前尝试进行分析。当您看到大部分处理过程时,您可能会感到惊讶。我使用hotshot来分析我的代码,并且非常满意。您可以在这里找到有关Python分析的良好介绍http://onlamp.com/pub/a/python/2005/12/15/profiling.html


0

你是否考虑过查看自然语言工具包。它包括许多用于处理文本语料库的优秀功能,还有一个很酷的FreqDist类,可以像字典一样(有键)和列表一样(切片)进行操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接