在Python中使用字典将字符串替换成ID

3
我有一个词典文件,每行都包含一个单词。
titles-sorted.txt
 a&a    
 a&b    
 a&c_bus    
 a&e    
 a&f    
 a&m    
 ....

每个单词的行号即为该单词的ID。

我还有另一个文件,每行包含用制表符分隔的一组单词。

a.txt

 a_15   a_15_highway_(sri_lanka)    a_15_motorway   a_15_motorway_(germany) a_15_road_(sri_lanka)

我希望能够将所有存在于字典中的单词替换为其对应的id,以便输出结果如下:
    3454    2345    123   5436     322 .... 

所以我写了以下Python代码来实现这个功能:

 f = open("titles-sorted.txt")
 lines = f.readlines()
 titlemap = {}
 nr = 1
 for l in lines:
     l = l.replace("\n", "")
     titlemap[l.lower()] = nr
     nr+=1

 fw = open("a.index", "w")
 f = open("a.txt")
 lines = f.readlines()
 for l in lines:
     tokens = l.split("\t")
     if tokens[0] in titlemap.keys():
            fw.write(str(titlemap[tokens[0]]) + "\t")
            for t in tokens[1:]:
                    if t in titlemap.keys():
                            fw.write(str(titlemap[t]) + "\t")
            fw.write("\n")

 fw.close()
 f.close()

但是这段代码运行速度非常慢,让我怀疑我是否做得正确。

这种方法是否高效?


有多少个键,然后您要替换多少个单词? - SirParselot
1
有一件事是,您正在调用readlines,其实不需要这样做,并且取决于您使用的Python版本,它可能会一次性读取整个文件,这并不必要。 您可以简单地使用for l in f: - njzk2
1
@earnshae,这段代码看起来应该按顺序处理每行和每个行内的令牌以保持输出文件的顺序,因此我不明白您如何在其中包含线程。 - njzk2
有多慢才算慢?也许只是需要很长时间。 - Peter Wood
你的瓶颈在于 .write() 方法。只有在最后调用 write,并在某个缓冲区(可能是列表)中累积要写入的字符串。 - UrbKr
显示剩余4条评论
3个回答

4
写入循环包含许多对write的调用,通常效率较低。如果每行或每个文件只写入一次,您可以提高速度(如果文件足够小)。
tokens = l.split("\t")
fw.write('\t'.join(fw.write(str(titlemap[t])) for t in tokens if t in titlemap)
fw.write("\n")

甚至可以:
lines = []
for l in f:
    lines.append('\t'.join(fw.write(str(titlemap[t])) for t in l.split('\t') if t in titlemap)
fw.write('\n'.join(lines))

此外,如果您的令牌被多次使用,您可以在读取时将它们转换为字符串以节省时间:
titlemap = {l.strip().lower(): str(index) for index, l in enumerate(f, start=1)}

1
因此,我怀疑这取决于您正在运行的操作系统和具体的Python实现(比我聪明的人可能能够提供一些澄清),但我对发生的事情有所怀疑:
每次调用write时,一些所需写入请求的数量被写入缓冲区,然后一旦缓冲区满了,这些信息就会被写入文件。该文件需要从硬盘获取(因为它不存在于主内存中)。因此,在等待几毫秒的时间内,您的计算机会暂停,以等待从硬盘中获取块并将其写入。另一方面,您可以在几个纳秒内解析字符串并查找哈希映射表,因此您将花费大量时间等待写入请求完成!
与其立即写入,不如将要写入的行列表保存起来,然后只在最后一次性地写入它们,或者如果您正在处理一个超出主内存容量的巨大文件,则在解析了一定数量的行后再进行写入。
这样可以优化写入磁盘的过程,因为您可以一次写入多个块(再次取决于Python和操作系统如何处理写入调用)。

0
如果我们按照目前的建议并进一步清理您的代码(例如,删除不必要的.keys()调用),那么以下内容对于您的需求仍然太慢吗?
title_map = {}

token_file = open("titles-sorted.txt")

for number, line in enumerate(token_file):
    title_map[line.rstrip().lower()] = str(number + 1)

token_file.close()

input_file = open("a.txt")
output_file = open("a.index", "w")

for line in input_file:
    tokens = line.split("\t")

    if tokens[0] in title_map:
        output_list = [title_map[tokens[0]]]
        output_list.extend(title_map[token] for token in tokens[1:] if token in title_map)
        output_file.write("\t".join(output_list) + "\n")

output_file.close()
input_file.close()

如果速度仍然太慢,请给我们提供更多的数据,包括您两个输入文件中每个文件的行数估计。

删除fw.write()调用的#显著减少了速度,现在代码运行非常快。谢谢!但是我以为 'tokens[0] in title_map' 和 'tokens[0] in title_map.keys()' 是等价的。显式调用keys()更昂贵吗? - pandagrammer
"in map" 是一种哈希时间查找。"in map.keys()" 取决于 Python 版本。在 Python 2 中,它是一个列表的线性搜索。在 Python 3 中,它是对 dict_keys 对象的查询,这在最好的情况下是哈希时间,但可能不太一样。 - cdlane

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