Примечание: Этот вопрос был задан до того, как ОП изменил тег python для тега awk .
Если вы не возражаете против порядка элементов, которые вы можете сделать:
with open("in.csv", "r") as file:
lines = set()
for line in file:
lines.add(frozenset(line.strip("\n").split(",")))
with open("out.csv", "w") as file:
for line in lines:
file.write(",".join(line)+"\n")
Выход:
Col2,COL1,Col3
EFG,454,ABC
DEF,123,ABC
Обратите внимание, что вы можете обрабатывать первую строку (заголовки) особым образом, чтобы не потерять их порядок.
Но если порядок имеет значение, вы можете использовать код из Поддержание порядка элементов в замороженном наборе :
from itertools import filterfalse
def unique_everseen(iterable, key=None):
seen = set()
seen_add = seen.add
if key is None:
for element in filterfalse(seen.__contains__, iterable):
seen_add(element)
yield element
else:
for element in iterable:
k = key(element)
if k not in seen:
seen_add(k)
yield element
with open("in.csv", "r") as file:
lines = []
for line in file:
lines.append(line.strip("\n").split(","))
with open("out.csv", "w") as file:
for line in unique_everseen(lines, key=frozenset):
file.write(",".join(line)+"\n")
Выход:
COL1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
ОП сказал, что оба кода не работают с большими файлами (1,8 Гб). Я думаю, это может быть связано с тем, что оба кода хранят файл в списке с использованием оперативной памяти, а файл объемом 1,8 ГБ может занимать все доступное пространство в памяти.
Чтобы решить это, я сделал еще несколько попыток. К сожалению, я должен сказать, что все они медленнее по сравнению с первой попыткой. Первые коды жертвуют потреблением ОЗУ ради скорости, но следующие коды жертвуют скоростью, процессором и жестким диском для меньшего потребления ОЗУ (вместо того, чтобы использовать весь размер файла в ОЗУ, они занимают менее 50 МБ).
Поскольку во всех этих примерах требуется более интенсивное использование жесткого диска, желательно иметь файлы «input» и «output» на разных жестких дисках.
Моя первая попытка использовать меньше ОЗУ с помощью модуля shelve
:
import shelve, os
with shelve.open("tmp") as db:
with open("in.csv", "r") as file:
for line in file:
l = line.strip("\n").split(",")
l.sort()
db[",".join(l)] = l
with open("out.csv", "w") as file:
for v in db.values():
file.write(",".join(v)+"\n")
os.remove("temp.bak")
os.remove("temp.dat")
os.remove("temp.dir")
К сожалению, этот код занимает в сто раз больше, чем первые два кода, которые используют оперативную память.
Еще одна попытка:
with open("in.csv", "r") as fileRead:
# total = sum(1 for _ in fileRead)
# fileRead.seek(0)
# i = 0
with open("out.csv", "w") as _:
pass
with open("out.csv", "r+") as fileWrite:
for lineRead in fileRead:
# i += 1
line = lineRead.strip("\n").split(",")
lineSet = set(line)
write = True
fileWrite.seek(0)
for lineWrite in fileWrite:
if lineSet == set(lineWrite.strip("\n").split(",")):
write = False
if write:
pass
fileWrite.write(",".join(line)+"\n")
# if i / total * 100 % 1 == 0: print(f"{i / total * 100}% ({i} / {total})")
Это немного быстрее, но не намного.
Если ваш компьютер имеет несколько ядер, вы можете попробовать multiprocessing :
from multiprocessing import Process, Queue, cpu_count
from os import remove
def slave(number, qIn, qOut):
name = f"slave-{number}.csv"
with open(name, "w") as file:
pass
with open(name, "r+") as file:
while True:
if not qIn.empty():
get = qIn.get()
if get == False:
qOut.put(name)
break
else:
write = True
file.seek(0)
for line in file:
if set(line.strip("\n").split(",")) == get[1]:
write = False
break
if write:
file.write(get[0])
def master():
qIn = Queue(1)
qOut = Queue()
slaves = cpu_count()
slavesList = []
for n in range(slaves):
slavesList.append(Process(target=slave, daemon=True, args=(n, qIn, qOut)))
for s in slavesList:
s.start()
with open("in.csv", "r") as file:
for line in file:
lineSet = set(line.strip("\n").split(","))
qIn.put((line, lineSet))
for _ in range(slaves):
qIn.put(False)
for s in slavesList:
s.join()
slavesList = []
with open(qOut.get(), "r+") as fileMaster:
for x in range(slaves-1):
file = qOut.get()
with open(file, "r") as fileSlave:
for lineSlave in fileSlave:
lineSet = set(lineSlave.strip("\n").split(","))
write = True
fileMaster.seek(0)
for lineMaster in fileMaster:
if set(lineMaster.strip("\n").split(",")) == lineSet:
write = False
break
if write:
fileMaster.write(lineSlave)
slavesList.append(Process(target=remove, daemon=True, args=(file,)))
slavesList[-1].start()
for s in slavesList:
s.join()
Как видите, у меня есть неутешительная задача сказать вам, что обе мои попытки работают очень медленно. Я надеюсь, что вы найдете лучший подход, в противном случае для выполнения операций с 1,8 ГБ данных потребуются часы, если не дни (реальное время будет в основном зависеть от количества повторных значений, что сокращает время).
Новая попытка: вместо сохранения каждого в файле, эта попытка сохраняет активную часть в памяти, а затем записывает в файл, чтобы быстрее обрабатывать фрагменты. Затем, фрагменты должны быть прочитаны снова, используя один из выше методов:
lines = set()
maxLines = 1000 # This is the amount of lines that will be stored at the same time on RAM. Higher numbers are faster but requeires more RAM on the computer
perfect = True
with open("in.csv", "r") as fileRead:
total = sum(1 for _ in fileRead)
fileRead.seek(0)
i = 0
with open("tmp.csv", "w") as fileWrite:
for line in fileRead:
if (len(lines) < maxLines):
lines.add(frozenset(line.strip("\n").split(",")))
i += 1
if i / total * 100 % 1 == 0: print(f"Reading {i / total * 100}% ({i} / {total})")
else:
perfect = False
j = 0
for line in lines:
j += 1
fileWrite.write(",".join(line) + "\n")
if i / total * 100 % 1 == 0: print(f"Storing {i / total * 100}% ({i} / {total})")
lines = set()
if (not perfect):
use_one_of_the_above_methods() # Remember to read the tmp.csv and not the in.csv
Это может повысить скорость. Вы можете изменить maxLines
на любое число, которое вам нравится, помните, что чем больше число, тем выше скорость (не уверен, что действительно большие числа делают наоборот), но больше потребление оперативной памяти.