文本文件包含两列 - 索引号(5个空格)和字符(30个空格)。 它按字典顺序排列。我想执行二进制搜索来搜索关键字。
答案 0 :(得分:9)
使用Python的内置bisect模块,这是一种有趣的方法。
import bisect
import os
class Query(object):
def __init__(self, query, index=5):
self.query = query
self.index = index
def __lt__(self, comparable):
return self.query < comparable[self.index:]
class FileSearcher(object):
def __init__(self, file_pointer, record_size=35):
self.file_pointer = file_pointer
self.file_pointer.seek(0, os.SEEK_END)
self.record_size = record_size + len(os.linesep)
self.num_bytes = self.file_pointer.tell()
self.file_size = (self.num_bytes // self.record_size)
def __len__(self):
return self.file_size
def __getitem__(self, item):
self.file_pointer.seek(item * self.record_size)
return self.file_pointer.read(self.record_size)
if __name__ == '__main__':
with open('data.dat') as file_to_search:
query = raw_input('Query: ')
wrapped_query = Query(query)
searchable_file = FileSearcher(file_to_search)
print "Located @ line: ", bisect.bisect(searchable_file, wrapped_query)
答案 1 :(得分:3)
你需要做二分搜索吗?如果没有,请尝试将平面文件转换为cdb (constant database)。这将为您提供非常快速的哈希查找,以查找给定单词的索引:
import cdb
# convert the corpus file to a constant database one time
db = cdb.cdbmake('corpus.db', 'corpus.db_temp')
for line in open('largecorpus.txt', 'r'):
index, word = line.split()
db.add(word, index)
db.finish()
在单独的脚本中,针对它运行查询:
import cdb
db = cdb.init('corpus.db')
db.get('chaos')
12345
答案 2 :(得分:1)
如果您需要在文件中找到单个关键字:
line_with_keyword = next((line for line in open('file') if keyword in line),None)
if line_with_keyword is not None:
print line_with_keyword # found
要查找多个关键字,您可以将set()
用作@kriegar suggested:
def extract_keyword(line):
return line[5:35] # assuming keyword starts on 6 position and has length 30
with open('file') as f:
keywords = set(extract_keyword(line) for line in f) # O(n) creation
if keyword in keywords: # O(1) search
print(keyword)
您可以使用上面的dict()
代替set()
来保存index
信息。
以下是如何对文本文件进行二进制搜索:
import bisect
lines = open('file').readlines() # O(n) list creation
keywords = map(extract_keyword, lines)
i = bisect.bisect_left(keywords, keyword) # O(log(n)) search
if keyword == keywords[i]:
print(lines[i]) # found
与set()
变体相比没有优势。
注意:除第一个之外的所有变体都将整个文件加载到内存中。 FileSearcher()
suggested by @Mahmoud Abdelkader不需要将整个文件加载到内存中。
答案 3 :(得分:1)
我写了一个简单的Python 3.6+包就可以做到这一点。 (有关更多信息,请参见其github页!)
安装:pip install binary_file_search
示例文件:
1,one
2,two_a
2,two_b
3,three
用法:
from binary_file_search.BinaryFileSearch import BinaryFileSearch
with BinaryFileSearch('example.file', sep=',', string_mode=False) as bfs:
# assert bfs.is_file_sorted() # test if the file is sorted.
print(bfs.search(2))
结果:[[2, 'two_a'], [2, 'two_b']]
答案 4 :(得分:0)
考虑使用集合代替二进制搜索来查找文件中的关键字。
集:
O(n)创建,O(1)查找,O(1)插入/删除
如果您的输入文件以空格分隔,则:
f = open('file')
keywords = set( (line.strip().split(" ")[1] for line in f.readlines()) )
f.close()
my_word in keywords
<returns True or False>
字典:
f = open('file')
keywords = dict( [ (pair[1],pair[0]) for pair in [line.strip().split(" ") for line in f.readlines()] ] )
f.close()
keywords[my_word]
<returns index of my_word>
二进制搜索是:
O(n log n)create,O(log n)lookup
编辑:对于5个字符和30个字符的情况,您可以使用字符串切片
f = open('file')
keywords = set( (line[5:-1] for line in f.readlines()) )
f.close()
myword_ in keywords
or
f = open('file')
keywords = dict( [(line[5:-1],line[:5]) for line in f.readlines()] )
f.close()
keywords[my_word]
答案 5 :(得分:0)
很有可能,在具有未知长度记录的已排序文本文件上执行二进制搜索,通过重复二等分范围,并向前读取行终止符,效率略有下降。这是我在第一个字段中通过一个带有2个标题行的csv文件查找的内容。给它一个打开的文件,以及要查找的第一个字段。为您的问题修改它应该相当容易。偏移零点的第一行匹配将失败,因此可能需要特殊配置。在我的情况下,前两行是标题,并被跳过。
请原谅我下面没有抛光的蟒蛇。我使用此函数和类似函数直接从Maxmind分发的CSV文件中执行GeoCity Lite纬度和经度计算。
希望这有帮助
========================================
# See if the input loc is in file
def look1(f,loc):
# Compute filesize of open file sent to us
hi = os.fstat(f.fileno()).st_size
lo=0
lookfor=int(loc)
# print "looking for: ",lookfor
while hi-lo > 1:
# Find midpoint and seek to it
loc = int((hi+lo)/2)
# print " hi = ",hi," lo = ",lo
# print "seek to: ",loc
f.seek(loc)
# Skip to beginning of line
while f.read(1) != '\n':
pass
# Now skip past lines that are headers
while 1:
# read line
line = f.readline()
# print "read_line: ", line
# Crude csv parsing, remove quotes, and split on ,
row=line.replace('"',"")
row=row.split(',')
# Make sure 1st fields is numeric
if row[0].isdigit():
break
s=int(row[0])
if lookfor < s:
# Split into lower half
hi=loc
continue
if lookfor > s:
# Split into higher half
lo=loc
continue
return row # Found
# If not found
return False