我有一个相对较大的文本文件(大约7米行),我想在它上面运行一个特定的逻辑,我将尝试解释如下:
A1KEY1
A2KEY1
B1KEY2
C1KEY3
D1KEY3
E1KEY4
我想计算密钥的出现频率,然后将频率为1的频率输出到一个文本文件中,将频率为2的频率输出到另一个文本文件中,将那些频率高于2的频率输出到另一个文本文件中。
这是我到目前为止的代码,但它在字典上的迭代速度非常缓慢,并且随着它的进展越来越慢。
def filetoliststrip(file):
file_in = str(file)
lines = list(open(file_in, 'r'))
content = [x.strip() for x in lines]
return content
dict_in = dict()
seen = []
fileinlist = filetoliststrip(file_in)
out_file = open(file_ot, 'w')
out_file2 = open(file_ot2, 'w')
out_file3 = open(file_ot3, 'w')
counter = 0
for line in fileinlist:
counter += 1
keyf = line[10:69]
print("Loading line " + str(counter) + " : " + str(line))
if keyf not in dict_in.keys():
dict_in[keyf] = []
dict_in[keyf].append(1)
dict_in[keyf].append(line)
else:
dict_in[keyf][0] += 1
dict_in[keyf].append(line)
for j in dict_in.keys():
print("Processing key: " + str(j))
#print(dict_in[j])
if dict_in[j][0] < 2:
out_file.write(str(dict_in[j][1]))
elif dict_in[j][0] == 2:
for line_in in dict_in[j][1:]:
out_file2.write(str(line_in) + "\n")
elif dict_in[j][0] > 2:
for line_in in dict_in[j][1:]:
out_file3.write(str(line_in) + "\n")
out_file.close()
out_file2.close()
out_file3.close()
我在带有8GB Ram的Windows PC i7上运行它,这应该不需要花费数小时才能执行。这是我将文件读入列表的方式的问题吗?我应该使用不同的方法吗?提前谢谢。
答案 0 :(得分:3)
你有多个点会降低你的代码速度 - 没有必要将整个文件加载到内存中只是为了再次迭代它,每次你想要查找时都不需要获取一个键列表( if key not in dict_in: ...
就足够了,而且速度非常快,你不需要保留行数,因为你可以检查行长度......仅举几例。
我将代码完全重构为:
import collections
dict_in = collections.defaultdict(list) # save some time with a dictionary factory
with open(file_in, "r") as f: # open the file_in for reading
for line in file_in: # read the file line by line
key = line.strip()[10:69] # assuming this is how you get your key
dict_in[key].append(line) # add the line as an element of the found key
# now that we have the lines in their own key brackets, lets write them based on frequency
with open(file_ot, "w") as f1, open(file_ot2, "w") as f2, open(file_ot3, "w") as f3:
selector = {1: f1, 2: f2} # make our life easier with a quick length-based lookup
for values in dict_in.values(): # use dict_in.itervalues() on Python 2.x
selector.get(len(values), f3).writelines(values) # write the collected lines
你几乎不会比那更有效率,至少在Python中。
请记住,这不能保证Python 3.7(或CPython 3.6)之前输出中的行顺序。但是,密钥本身内的顺序将被保留。如果您需要在上述Python版本之前保留行顺序,则必须保留单独的键顺序列表并对其进行迭代以按顺序获取dict_in
值。
答案 1 :(得分:1)
第一个功能:
def filetoliststrip(file):
file_in = str(file)
lines = list(open(file_in, 'r'))
content = [x.strip() for x in lines]
return content
此处生成的原始行列表仅被剥离。这将需要大约两倍于必要的内存,同样重要的是,几次传递不适合缓存的数据。我们也不需要反复制作str
件事。所以我们可以稍微简化一下:
def filetoliststrip(filename):
return [line.strip() for line in open(filename, 'r')]
这仍然会产生一个列表。如果我们只读取数据一次,而不是存储每一行,请将[]
替换为()
,将其转换为生成器表达式;在这种情况下,由于行在程序结束之前实际上在内存中保持完整,我们只保存列表的空间(在您的情况下仍然至少为30MB)。
然后我们有了主要的解析循环(我按照我的想法调整了缩进):
counter = 0
for line in fileinlist:
counter += 1
keyf = line[10:69]
print("Loading line " + str(counter) + " : " + str(line))
if keyf not in dict_in.keys():
dict_in[keyf] = []
dict_in[keyf].append(1)
dict_in[keyf].append(line)
else:
dict_in[keyf][0] += 1
dict_in[keyf].append(line)
这里有几个次优的东西。
首先,计数器可以是enumerate
(当您没有可迭代时,有range
或itertools.count
)。改变这一点有助于提高清晰度并降低错误风险。
for counter, line in enumerate(fileinlist, 1):
其次,在一个操作中形成字符串比从位添加字符串更有效:
print("Loading line {} : {}".format(counter, line))
第三,没有必要提取字典成员检查的密钥。在Python 2中构建一个新列表,这意味着复制键中保存的所有引用,并且每次迭代都会变慢。在Python 3中,它仍然意味着不必要地构建关键视图对象。如果需要检查,只需使用keyf not in dict_in
。
第四,确实不需要检查。在查找失败时捕获异常与if检查一样快,并且在if检查之后重复查找几乎肯定会更慢。就此而言,停止重复查找:
try:
dictvalue = dict_in[keyf]
dictvalue[0] += 1
dictvalue.append(line)
except KeyError:
dict_in[keyf] = [1, line]
这是一种常见模式,但我们有两个标准库实现:Counter
和defaultdict
。我们可以在这里使用,但是当你只想要计数时,Counter更实用。
from collections import defaultdict
def newentry():
return [0]
dict_in = defaultdict(newentry)
for counter, line in enumerate(fileinlist, 1):
keyf = line[10:69]
print("Loading line {} : {}".format(counter, line))
dictvalue = dict_in[keyf]
dictvalue[0] += 1
dictvalue.append(line)
使用defaultdict
,我们不用担心条目是否存在。
我们现在到达输出阶段。我们再次进行了不必要的查找,所以让我们将它们减少到一次迭代:
for key, value in dict_in.iteritems(): # just items() in Python 3
print("Processing key: " + key)
#print(value)
count, lines = value[0], value[1:]
if count < 2:
out_file.write(lines[0])
elif count == 2:
for line_in in lines:
out_file2.write(line_in + "\n")
elif count > 2:
for line_in in lines:
out_file3.write(line_in + "\n")
仍有一些烦恼。我们重复了编写代码,它构建了其他字符串(在"\n"
上标记),并且每个案例都有一大堆相似的代码。实际上,重复可能会导致错误:out_file
中的单个事件没有新行分隔符。让我们分析出真正不同的东西:
for key, value in dict_in.iteritems(): # just items() in Python 3
print("Processing key: " + key)
#print(value)
count, lines = value[0], value[1:]
if count < 2:
key_outf = out_file
elif count == 2:
key_outf = out_file2
else: # elif count > 2: # Test not needed
key_outf = out_file3
key_outf.writelines(line_in + "\n" for line_in in lines)
我已经离开了新行连接,因为将它们作为单独的调用混合起来会更复杂。该字符串是短暂的,它的目的是让换行符位于同一位置:它使得在操作系统级别上不太可能通过并发写入来分解该行。
你会注意到这里有Python 2和3的区别。如果首先在Python 3中运行,那么你的代码很可能并不那么慢。存在一个名为six的兼容性模块,用于编写更容易在其中运行的代码;它可以让你使用例如six.viewkeys
和six.iteritems
以避免此问题。
答案 2 :(得分:0)
您可以立即将非常大的文件加载到内存中。如果您实际上不需要线路,并且只需要处理它,请使用生成器。它的内存效率更高。
Counter
是一个集合,其中元素存储为字典键,其计数存储为字典值。您可以使用它来计算键的频率。然后简单地遍历新的dict
并将密钥附加到相关文件:
from collections import Counter
keys = ['A1KEY1', 'A2KEY1', 'B1KEY2', 'C1KEY3', 'D1KEY3', 'E1KEY4']
count = Counter(keys)
with open('single.txt') as f1:
with open('double.txt') as f2:
with open('more_than_double.txt') as f3:
for k, v in count.items():
if v == 1:
f1.writelines(k)
elif v == 2:
f2.writelines(k)
else:
f3.writelines(k)