在 python 中查找一个文件中不在另一个文件中的所有数字

Find all the numbers in one file that are not in another file in python

有两个文件,例如 FileA 和 FileB,我们需要找到 FileA 中不存在于 FileB 中的所有数字。 FileA 中的所有数字都已排序,FileB 中的所有数字也已排序。例如,

输入:

FileA = [1, 2, 3, 4, 5, ...]
FileB = [1, 3, 4, 6, ...]

输出:

[2, 5, ...]

内存非常有限,一次甚至无法将整个文件加载到内存中。还需要线性或更小的时间复杂度。

因此,如果文件足够小以适合内存,我们可以加载它们并将其内容初始化为两个集合,然后取一个集合差异,以便在 O(1) 或常数时间复杂度内解决问题.

set(contentsofFileA)-set(contentsofFileB)

但是由于文件太大,它们将无法完全加载到内存中,所以这是不可能的。

另外,另一种方法是使用暴力法进行批处理。因此,我们从 FileA 加载一个块或一批数据,然后从 FileB 加载一个批次,然后比较它,然后从 FileB 加载下一个块,依此类推。然后在 FileA 块检查 FileB 中的所有元素之后,然后从 FileA 加载下一批,然后继续。但这会产生 O(n^2) 或二次时间复杂度,并且对于包含大量条目的非常大的文件来说效率不高。

问题需要以线性或更小的时间复杂度解决,并且不需要将整个文件加载到内存中。有帮助吗?

您可以组合 itertools.groupby (doc) and heapq.merge (doc) 以延迟遍历 FileAFileB(只要文件已排序,它就可以工作!)

FileA = [1, 1, 2, 3, 4, 5]
FileB = [1, 3, 4, 6]

from itertools import groupby
from heapq import merge

gen_a = ((v, 'FileA') for v in FileA)
gen_b = ((v, 'FileB') for v in FileB)

for v, g in groupby(merge(gen_a, gen_b, key=lambda k: int(k[0])), lambda k: int(k[0])):
    if any(v[1] == 'FileB' for v in g):
        continue
    print(v)

打印:

2
5

编辑(从文件中读取):

from itertools import groupby
from heapq import merge

gen_a = ((int(v.strip()), 1) for v in open('3k.txt'))
gen_b = ((int(v.strip()), 2) for v in open('2k.txt'))

for v, g in groupby(merge(gen_a, gen_b, key=lambda k: k[0]), lambda k: k[0]):
    if any(v[1] == 2 for v in g):
        continue
    print(v)

基准:

正在生成包含 10_000_000 项的文件:

seq 0 3 10000000 > 3k.txt
seq 0 2 10000000 > 2k.txt

脚本需要大约 10 秒才能完成:

real    0m10,656s
user    0m10,557s
sys 0m0,076s

如果你想逐行读取文件,因为你没有那么多内存,你需要一个线性解决方案,如果你的文件是基于行的,你可以用 iter 来做到这一点,否则见 this :

首先在您的终端中,您可以执行此操作以生成一些测试文件:

seq 0 3 100 > 3k.txt
seq 0 2 100 > 2k.txt

那么你 运行 这个代码:

i1 = iter(open("3k.txt"))
i2 = iter(open("2k.txt"))
a = int(next(i1))
b = int(next(i2))
aNotB = []
# bNotA = []
while True:
    try:
        if a < b:
            aNotB += [a]
            a = int(next(i1, None))
        elif a > b:
            # bNotA += [a]
            b = int(next(i2, None))
        elif a == b:
            a = int(next(i1, None))
            b = int(next(i2, None))
    except TypeError:
        if not b:
            aNotB += list(i1)
            break
        else:
            # bNotA += list(i1)
            break
print(aNotB)

输出:

[3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 87, 93, 99] If you want both the result for aNotB and bNotA you can uncomment those two lines.

与 Andrej Kesely 的回答的时间比较:

$ seq 0 3 1000000 > 3k.txt
$ seq 0 2 1000000 > 2k.txt
$ time python manual_iter.py        
python manual_iter.py  0.38s user 0.00s system 99% cpu 0.387 total
$ time python heapq_groupby.py        
python heapq_groupby.py  1.11s user 0.00s system 99% cpu 1.116 total

基于文件读取的简单解决方案(假设每一行都有一个数字):

results = []
with open('file1.csv') as file1, open('file2.csv') as file2:
        var1 = file1.readline()
        var2 = file2.readline()
        while var1:
            while var1 and var2:
                if int(var1) < int(var2):
                    results.append(int(var1))
                    var1 = file1.readline()
                elif int(var1) > int(var2):
                    var2 = file2.readline()
                elif int(var1) == int(var2):
                    var1 = file1.readline()
                    var2 = file2.readline()
            if var1:
                results.append(int(var1))
                var1 = file1.readline()
print(results)
output = [2, 5, 7, 9]

随着文件的排序,您可以一次遍历每一行,如果文件 A 的行少于文件 B 的行,那么您就知道 A 不在 B 中,因此您只增加文件 A然后再次检查。如果 A 中的行大于 B 中的行,那么您知道 B 不在 A 中,因此您只增加文件 B。如果 A 和 B 相等,那么您知道行在两者中,因此增加两个文件。虽然在您最初的问题中您说您对 A 而不是 B 中的条目感兴趣,但这个答案将扩展它并给出 B 中的条目而不是 A。这扩展了灵活性但仍然允许您只打印 A 中的条目而不是 B .

def strip_read(file):
    return file.readline().rstrip()

in_a_not_b = []
in_b_not_a = []
with open("fileA") as A:
    with open("fileB") as B:
        Aline = strip_read(A)
        Bline = strip_read(B)
        while Aline or Bline:
            if Aline < Bline and Aline:
                in_a_not_b.append(Aline)
                Aline = strip_read(A)
            elif Aline > Bline and Bline:
                in_b_not_a.append(Bline)
                Bline = strip_read(B)
            else:
                Aline = strip_read(A)
                Bline = strip_read(B)

print("in A not in B", in_a_not_b, "\nin B not in A", in_b_not_a)

我的示例文件的输出

in A not in B ['2', '5', '7'] 
in B not in A ['6']

这类似于经典的 Knuth 排序和搜索。 不妨考虑阅读stack question, on-line lecture notes pdf, and Wikipedia。 堆栈问题提到了一些我同意的事情,那就是使用 unix sort 命令。始终,始终使用您自己的数据进行测试,以确保选择的方法对您的数据最有效,因为其中一些算法依赖于数据。