Найти все числа в одном файле, которых нет в другом файле в 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) или квадратичного времени и будет неэффективной для очень большого файла с большими записями.
Проблема должна быть решена с линейной или меньшей временной сложностью и без загрузки всех файлов в память. Любая помощь?
Ответы
Ответ 1
Если вы хотите читать файлы построчно, поскольку у вас не так много памяти, и вам нужно линейное решение, вы можете сделать это с помощью iter, если ваши файлы основаны на строках, в противном случае см. это:
Сначала вы можете сделать это в своем терминале, чтобы сгенерировать несколько тестовых файлов:
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 и bNotA you can uncomment those two lines.
Сравнение сроков с ответом Андрея Кесели:
$ 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
Ответ 2
Когда файлы отсортированы, вы можете просто перебирать каждую строку за раз, если строка файла 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']
Ответ 3
Вы можете комбинировать itertools.groupby
(doc) и heapq.merge
(doc), чтобы лениво перебирать FileA
и FileB
(это работает, пока файлы сортируются!)
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)
Benchmark:
Создание файлов из 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
Ответ 4
Простое решение, основанное на чтении файлов (при условии, что каждая строка содержит номер):
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]
Ответ 5
Это похоже на классическую сортировку и поиск Кнута.
Вы можете рассмотреть возможность прочтения стека вопросов,
интерактивные заметки для лекций pdf и Википедия.
Вопрос стека упоминает кое-что, с чем я согласен, используя команду сортировки unix. Всегда проверяйте свои собственные данные, чтобы убедиться, что выбранный метод является наиболее эффективным для ваших данных, поскольку некоторые из этих алгоритмов зависят от данных.