高效的Python方式来处理两个巨大的文件?

时间:2013-09-24 23:47:55

标签: python file range

我正在研究一个问题,我必须找到一个数字是否在一定范围内。但是,由于我正在处理的文件有数十万行,所以问题很复杂。

下面我尝试用尽可能简单的语言解释问题。

以下是我输入文件的简要说明:

文件 Ranges.txt 包含一些范围,其中min和max是制表符分隔的。

10 20
30 40
60 70

这可以有大约10,000,000条带有范围的这样的线。

注意:范围从不重叠。

文件 Numbers.txt 有一个数字列表和一些与每个数字相关联的值。

12 0.34
22 0.14
34 0.79
37 0.87

等等。同样,有成千上万的此类行具有数字及其相关值。

我希望从 Numbers.txt 中获取每个数字,并检查它是否属于 Ranges.txt 中的任何范围。

对于属于某个范围的所有此类数字,我必须得到其相关值的平均值(即每个范围的平均值)。

例如。在上面 Numbers.txt 中的示例中,有两个数字34和37在 Ranges.txt 范围内,在30-40范围内,因此范围为30-40我必须计算相关值34和37的平均值。(即0.79和0.87的平均值),即0.82

我的最终输出文件应该是 Ranges.txt ,但是每个范围内的所有数字的关联值的平均值。类似的东西:

Output.txt的

10 20 <mean>
30 40 0.82
60 70 <mean>

等等。

非常感谢有关如何在Python中有效编写这些内容的任何帮助和想法。

3 个答案:

答案 0 :(得分:4)

显然,您需要从Numbers.txt中对Ranges.txt中的每一行运行每一行。

您可以迭代Numbers.txt,并且,对于每一行,迭代Ranges.txt。但这将需要永远,读取整个Ranges.txt文件数百万次。

你可以将它们都读入内存,但这需要大量存储空间,这意味着在完成两个文件的读取和预处理之前,你将无法进行任何处理。

所以,你要做的就是将Ranges.txt读入内存一次,然后将其存储为一对ints的列表,但是懒惰地读取Numbers.txt,遍历每个数字的列表。

这种事情总是出现。通常,您希望将更大的集合放入外部循环中,并使其尽可能延迟,而较小的集合进入内部循环,并进行预处理以使其尽可能快。但是如果可以更有效地预处理更大的集合(并且你有足够的内存来存储它!),那就改变它。


说到预处理,你可以做的不仅仅是阅读一对整数的列表。如果您对Ranges.txt进行了排序,您可以找到最接近的范围,而不是通过二等分来检查(18步),而不是详尽地检查每个范围(100000步)。

这对stdlib来说有点痛苦,因为使用bisect时很容易犯一个错误,但是有很多ActiveState配方可以让它更容易(包括一个{{3} }},更不用说像linked from the official docsblist这样的第三方模块,它们在简单的OO界面中为您提供了一个排序集合。


所以,像这样的伪代码:

with open('ranges.txt') as f:
    ranges = sorted([map(int, line.split()) for line in f])
range_values = {}
with open('numbers.txt') as f:
    rows = (map(int, line.split()) for line in f)
    for number, value in rows:
        use the sorted ranges to find the appropriate range (if any)
        range_values.setdefault(range, []).append(value)
with open('output.txt') as f:
    for r, values in range_values.items():
        mean = sum(values) / len(values)
        f.write('{} {} {}\n'.format(r[0], r[1], mean))

顺便说一句,如果解析结果比在每行调用split更复杂,我建议使用csv模块...但看起来不会这是一个问题。


如果你不能将Ranges.txt放入内存,但是可以适合Numbers.txt怎么办?好吧,你可以对它进行排序,然后迭代Ranges.txt,找到排序数字中的所有匹配项,并将结果写出该范围。

这有点复杂,因为你需要bisect_left和bisect_right并迭代它们之间的所有内容。但这是唯一更难的方式。 (在这里,第三方课程将提供更多帮助。例如,使用bintrees.FastRBTree作为已排序的集合,它只是sorted_number_tree[low:high]。)


如果范围可以重叠,你需要更聪明一些 - 你必须找到最接近的范围而不越过开始,并且最近的范围不会结束,并检查两者之间的所有内容。但主要技巧与上一版本使用的完全相同。唯一的另一个技巧是保留两个范围副本,一个按起始值排序,另一个按结尾排序,你需要将其中一个作为另一个的索引映射,而不仅仅是普通列表。 / p>

答案 1 :(得分:1)

天真的方法是将Numbers.txt按数字顺序读入某个结构,然后读取Ranges的每一行,我们使用二进制搜索找到范围中的最小数字,并读取高于该数字的数字。找到范围内的所有数据,以便您可以生成相应的输出行。

我认为问题在于你不能在内存中拥有所有数字。

因此,您可以分阶段完成问题,其中每个阶段读取一部分数字,然后执行上面概述的过程,但使用Ranges的注释版本,其中每行包含到目前为止的值的COUNT个已经产生了这个意思,并且会写一个类似注释的版本。

显然,初始传递不会有Ranges的注释版本,最终传递不会产生一个。

答案 2 :(得分:1)

看起来两个文件中的数据已经排序。如果没有,首先通过外部工具或使用Python对它们进行排序。

然后,您可以并行浏览这两个文件。您从Numbers.txt文件中读取一个数字,并查看它是否在Ranges.txt文件的范围内,从该文件中读取尽可能多的行来回答该问题。然后从Numbers.txt读取下一个数字,然后重复。这个想法类似于合并两个排序的数组,并且应该在O(n+m)时间运行,nm是文件的大小。如果需要对文件进行排序,则运行时间为O(n lg(n) + m lg(m))。这是我写的一个快速程序来实现这个:

import sys
from collections import Counter

class Gen(object):
    __slots__ = ('rfp', 'nfp', 'mn', 'mx', 'num', 'd', 'n')
    def __init__(self, ranges_filename, numbers_filename):
        self.d = Counter() # sum of elements keyed by range
        self.n = Counter() # number of elements keyed by range 

        self.rfp = open(ranges_filename)
        self.nfp = open(numbers_filename)
        # Read the first number and the first range values
        self.num = float(self.nfp.next()) # Current number
        self.mn, self.mx = [int(x) for x in self.rfp.next().split()] # Current range

    def go(self):
        while True:
            if self.mx < self.num:
                try:
                    self.mn, self.mx = [int(x) for x in self.rfp.next().split()]
                except StopIteration:
                    break
            else:   
                if self.mn <= self.num <= self.mx:
                    self.d[(self.mn, self.mx)] += self.num
                    self.n[(self.mn, self.mx)] += 1
                try:
                    self.num = float(self.nfp.next())
                except StopIteration:
                    break
        self.nfp.close()
        self.rfp.close()
        return self.d, self.n

def run(ranges_filename, numbers_filename):
    r = Gen(ranges_filename, numbers_filename)
    d, n = r.go()
    for mn, mx in sorted(d):
        s, N = d[(mn, mx)], n[(mn, mx)]
        if s:
            av = s/N
        else:
            av = 0
        sys.stdout.write('%d %d %.3f\n' % (mn, mx, av))

在每个文件中包含10,000,000个数字的文件中,上面的程序在我的计算机上运行大约1.5分钟而没有输出部分。