我正在研究一个问题,我必须找到一个数字是否在一定范围内。但是,由于我正在处理的文件有数十万行,所以问题很复杂。
下面我尝试用尽可能简单的语言解释问题。
以下是我输入文件的简要说明:
文件 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中有效编写这些内容的任何帮助和想法。
答案 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 docs或blist
这样的第三方模块,它们在简单的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)
时间运行,n
和m
是文件的大小。如果需要对文件进行排序,则运行时间为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分钟而没有输出部分。