我有一个具有几百万行的.csv。第一列是每个条目的ID,每个ID仅出现一次。第一列已排序。直观地说,使用分而治之算法可以高效地查询此文件。但是,我找不到与此相关的任何内容。
.csv文件示例:
+----+------------------+-----+
| id | name | age |
+----+------------------+-----+
| 1 | John Cleese | 34 |
+----+------------------+-----+
| 3 | Mary Poppins | 35 |
+----+------------------+-----+
| .. | ... | .. |
+----+------------------+-----+
| 87 | Barry Zuckerkorn | 45 |
+----+------------------+-----+
我不想将文件加载到内存中(太大),并且我不想使用数据库。我知道我可以只在sqlite中导入此文件,但是我有此数据的多个副本,出于多种原因,我希望避免这样做。
我可以忽略一个好的包裹吗?还是我必须自己写东西?
答案 0 :(得分:2)
好吧,我的理解是,您需要轻型数据库的某些功能,但是必须使用csv文本文件来保存数据。恕我直言,这可能是一个有问题的设计:过去几百行,我只会看到一个csv文件,是一种中间格式或交换格式。
由于这是一个非常不常见的设计,因此不太可能已经存在用于它的软件包-就我而言,我一无所知。因此,我想像两种可能的方法:扫描文件一次并建立索引id-> row_position,然后使用该索引进行查询。根据行的实际长度,您可以仅索引第n行以更改内存速度。但这要花一个索引文件
另一种方法是直接分治算法:使用stat / fstat获取文件大小,然后从文件中间开始搜索下一行。您会立即获得一个ID。如果您想要的ID是一个,那么您将赢得罚款,如果更大,则仅在上部递归,如果较小,则在下部递归。但是,由于必须搜索行尾,因此要准备好处理极端情况,就像永远不要在预期范围内找到行尾一样,或者在行末找到它。
答案 1 :(得分:0)
在Serges回答之后,我决定编写自己的实现,就在这里。它不允许换行,也没有处理有关.csv格式的许多详细信息。它假定.csv在第一列上排序,并且第一列是整数值。
import os
def query_sorted_csv(fname, id):
filesize = os.path.getsize(fname)
with open(fname) as fin:
row = look_for_id_at_location(fin, 0, filesize, id)
if not row:
raise Exception('id not found!')
return row
def look_for_id_at_location(fin, location_lower, location_upper, id, sep=',', id_column=0):
location = int((location_upper + location_lower) / 2)
if location_upper - location_lower < 2:
return False
fin.seek(location)
next(fin)
try:
full_line = next(fin)
except StopIteration:
return False
id_at_location = int(full_line.split(sep)[id_column])
if id_at_location == id:
return full_line
if id_at_location > id:
return look_for_id_at_location(fin, location_lower, location, id)
else:
return look_for_id_at_location(fin, location, location_upper, id)
row = query_sorted_csv('data.csv', 505)
您可以在200万行250MB .csv文件中每秒查询约4000个id。相比之下,您可以每秒查询3个ID,同时逐行循环浏览整个文件。