我有一个384MB的文本文件,有5000万行。每行包含2个以空格分隔的整数:键和值。该文件按键排序。我需要一种有效的方法来查找Python中大约200个键列表的值。
我目前的方法包括在下面。这需要30秒。必须有更高效的Python foo才能将其降低到最多几秒钟的合理效率。
# list contains a sorted list of the keys we need to lookup
# there is a sentinel at the end of list to simplify the code
# we use pointer to iterate through the list of keys
for line in fin:
line = map(int, line.split())
while line[0] == list[pointer].key:
list[pointer].value = line[1]
pointer += 1
while line[0] > list[pointer].key:
pointer += 1
if pointer >= len(list) - 1:
break # end of list; -1 is due to sentinel
编码二进制搜索+搜索解决方案(感谢kigurai!):
entries = 24935502 # number of entries
width = 18 # fixed width of an entry in the file padded with spaces
# at the end of each line
for i, search in enumerate(list): # list contains the list of search keys
left, right = 0, entries-1
key = None
while key != search and left <= right:
mid = (left + right) / 2
fin.seek(mid * width)
key, value = map(int, fin.readline().split())
if search > key:
left = mid + 1
else:
right = mid - 1
if key != search:
value = None # for when search key is not found
search.result = value # store the result of the search
答案 0 :(得分:11)
如果你只需要500万行中的200行,那么把它全部读入内存是一种浪费。我会对搜索键列表进行排序,然后使用seek()或类似的方法将二进制搜索应用于该文件。这样你就不会将整个文件读到内存中,我认为应该加快速度。
答案 1 :(得分:7)
S.Lotts的轻微优化答案:
from collections import defaultdict
keyValues= defaultdict(list)
targetKeys= # some list of keys as strings
for line in fin:
key, value = line.split()
if key in targetKeys:
keyValues[key].append( value )
由于我们使用的是字典而不是列表,因此密钥不一定是数字。这会将map()操作和字符串保存为每行的整数转换。如果您希望密钥是数字,那么只需为每个密钥执行一次,而不是为每个密钥执行一次,将转换作为结束。
答案 2 :(得分:4)
目前尚不清楚“list [pointer]”是什么。不过考虑一下。
from collections import defaultdict
keyValues= defaultdict(list)
targetKeys= # some list of keys
for line in fin:
key, value = map( int, line.split())
if key in targetKeys:
keyValues[key].append( value )
答案 3 :(得分:3)
我会使用内存映射:http://docs.python.org/library/mmap.html。
答案 4 :(得分:3)
这是文本文件
上的递归二进制搜索import os, stat
class IntegerKeyTextFile(object):
def __init__(self, filename):
self.filename = filename
self.f = open(self.filename, 'r')
self.getStatinfo()
def getStatinfo(self):
self.statinfo = os.stat(self.filename)
self.size = self.statinfo[stat.ST_SIZE]
def parse(self, line):
key, value = line.split()
k = int(key)
v = int(value)
return (k,v)
def __getitem__(self, key):
return self.findKey(key)
def findKey(self, keyToFind, startpoint=0, endpoint=None):
"Recursively search a text file"
if endpoint is None:
endpoint = self.size
currentpoint = (startpoint + endpoint) // 2
while True:
self.f.seek(currentpoint)
if currentpoint <> 0:
# may not start at a line break! Discard.
baddata = self.f.readline()
linestart = self.f.tell()
keyatpoint = self.f.readline()
if not keyatpoint:
# read returned empty - end of file
raise KeyError('key %d not found'%(keyToFind,))
k,v = self.parse(keyatpoint)
if k == keyToFind:
print 'key found at ', linestart, ' with value ', v
return v
if endpoint == startpoint:
raise KeyError('key %d not found'%(keyToFind,))
if k > keyToFind:
return self.findKey(keyToFind, startpoint, currentpoint)
else:
return self.findKey(keyToFind, currentpoint, endpoint)
在jEdit中创建的示例文本文件似乎有效:
>>> i = integertext.IntegerKeyTextFile('c:\\sampledata.txt')
>>> i[1]
key found at 0 with value 345
345
通过缓存找到的密钥并使用缓存来确定未来的起始搜索点,肯定可以改善它。
答案 5 :(得分:2)
如果您对文件格式有任何控制权,则“排序和二进制搜索”响应是正确的。细节是这只适用于固定大小和偏移的记录(好吧,我应该说它只适用于固定长度的记录)。
使用固定长度记录,您可以轻松搜索已排序文件周围的()以找到您的密钥。
答案 6 :(得分:0)
一种可能的优化是使用file.readlines(..)中的sizehint
选项进行一些缓冲。这允许您在内存中加载多行,总计大约sizehint
个字节。
答案 7 :(得分:0)
您需要使用seek()
实现二进制搜索