【问题标题】:Can I use bisect to print the content of a line?我可以使用 bisect 打印一行的内容吗?
【发布时间】:2014-12-03 19:39:41
【问题描述】:

我有一个文件,其中每一行都按字母顺序排列。该文件是 12Gb,这意味着我不能简单地逐行读取它。数据如下所示:

brown    0    1    0    1    2
fox      3    5    0    0    1
jumped   2    0    6    1    0

每行开头的单词都是唯一的。每行中的单词和数字由制表符分隔。我希望能够在文件中查询特定关键字。例如,如果我查询“fox”,程序应该返回“fox 3 5 0 0 1”。

似乎 bisect 模块是一个很好的候选者:https://docs.python.org/3.0/library/bisect.html

我发现了一个帖子,它使用 bisect 来找出关键字的行号:How do I perform binary search on a text file to search a keyword in python?

这就是代码的样子:

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)

with open('myfile') as file_to_search:
    query = 'fox\t' #token to query
    wrapped_query = Query(query)
    searchable_file = FileSearcher(file_to_search)
    linepos = bisect.bisect(searchable_file, wrapped_query)
    print "Located @ line: ", linepos
    #print content of line?

但是,我不知道如何实际打印该行的内容。我至少应该在某处添加一个 read 语句,但我不知道在哪里。

是否可以用 bisect 模块打印行的内容?

【问题讨论】:

  • 我认为使用 bisect 的引用方法不适用于您的数据文件,因为它具有可变长度记录(基于您在问题中提供的信息)。如果字段由空格分隔并定义为始终占据每条记录中的某些固定字符位置,那么它将起作用。
  • 你有一个 12Gb 的文件,由不同的单词开头的行组成?
  • 从您的代码中,searchable_file[linepos] 不会打印搜索到的行吗?
  • (大约 4200 万个不同的单词,数量不少)
  • 是的,正如@martineau 所说,它不起作用。

标签: python bisect


【解决方案1】:

如果您想使用 Python 解决方案,您可以执行以下操作:

  • MAX_LINE 字节的小块读取文件,每次向前移动固定偏移量
  • 偏移量决定块大小
  • 对于每个这样的读取,确定关键(一行中的第一个单词)
  • 这些键用作块的分隔符
  • 构造此类键的列表。该列表将按键排序
  • 您可以通过 pickle/json.dumps/... 将此类列表保存在某处。
  • 查询时,通过 bisect 找到你键所在块的索引
  • 完整读取该块并找到包含数据的密钥

这里是示例文件bigfile

abc 4
bar 2
baz 3
egg 6
foo 1
god 8
ham 5
sex 7

代码:

import os
from bisect import bisect

MAX_LINE = 7
BLOCK_SIZE = 10

def parse_chunks(filename):

    size = os.path.getsize(filename)
    chunks = []

    with open(filename, 'rb') as file:
        block = str(file.read(MAX_LINE*2))
        first_line = block[:block.find('\n') + 1]
        chunks.append(first_line.split()[0])

        pos = BLOCK_SIZE
        while pos < size:
            file.seek(pos)
            block = str(file.read(MAX_LINE*2))
            first_eol = block.find('\n')
            second_eol = block.find('\n', first_eol + 1)
            if first_eol == -1 or second_eol == -1:
                break

            line = block[first_eol + 1:second_eol]

            key = line.split()[0]
            chunks.append(key)

            pos += BLOCK_SIZE

    return chunks


if __name__ == '__main__':
    BLOCK_SIZE = 10
    filename = 'bigfile'
    chunks = parse_chunks(filename)

    query = 'abc'
    pos_before = bisect(chunks, query) - 1

    with open(filename, 'rb') as file:
        file.seek(pos_before*BLOCK_SIZE)
        block = str(file.read(BLOCK_SIZE + MAX_LINE))
        line_start = block.find(query)
        line_end = block.find('\n', line_start + 1)
        line = block[line_start:line_end]

        print(line)

在这个玩具示例中,我使用 10 字节的块大小,对于 12GB 文件,我建议您从 1M 开始。

【讨论】:

  • 我在实际数据上使用不同的 BLOCK_SIZE 值尝试了您的代码,但我总是收到 IOError: [Errno 22] Invalid argument on the following line: file.seek(pos_before*BLOCK_SIZE)跨度>
  • 再试一次,我已经修复了从第一个块查询时的错误
  • 现在它返回错误 IndexError: list index out of range on the following line: chunks.append(first_line.split()[0])
【解决方案2】:

下面的递归函数应该可以缩小搜索区间。我不确定您是否可以修改它以使其返回匹配项或 None 表示不匹配。

def bisearch(f, word, i, j)
    if (j-1)<1E6: return i,j

    k = (i+j)/2
    f.seek(k)


    while k<j:
        c = f.read(1)
        k = k+1
        if c == '\n': break
    else:
        # ??? no match ??? I'm not sure

    w = []
    while 1:
        c = f.read(1)
        if c == '\t': break
        w.append(c)

    w = "".join(w)
    if w == word:
         return k, k
    if w < word:
         return bisearch(f, word, k, j)
    else:
         return bisearch(f, word, i, k)

这里是一个使用示例

word = ...
f = open(...)

i,j = bisearch(f, word, 0, len_f)

f.seek(i)
if i==j:
    line = f.readline()
else:
#################### EDIT ################
#   OLD
#   buffer = f.read(1E6)
#   NEW
    buffer = f.read(j-i)
    lenw = len(word)
    for line in buffer.split('\n'):
        if line[:lenw] == word: break
    else:
        # no matches, SOS

result = process(line)

【讨论】:

  • 您的代码返回 TypeError: integer argument expected, got float on the line buffer = f.read(1E6)。我用 buffer = f.read(int(1E6)) 解决了它。主要问题是如何定义len_f。为了在我的 fox 示例中测试您的代码,我使用了 len_f = sum(1 for line in f),它运行良好。但是对于 12gb 的文件,以这种方式计算文件的长度可能需要几个小时。
  • (1) 抱歉类型错误,我没有测试我的代码... (2) The OS knows the length of the file... 查看解决方案的链接答案 (3) 它真的有效吗?
【解决方案3】:

尝试seeking 到有问题的行并使用readline

print "Located @ line: ", linepos
file_to_search.seek(linepos)
line = file_to_search.readline()

这是假设linepos 是行的位置,从文件开头开始计数。如果是按行号计算的位置,则需要在查找之前乘以每行的字节数。

print "Located @ line: ", linepos
file_to_search.seek(linepos * searchable_file.record_size)
line = file_to_search.readline()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-04
    • 2019-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多