У меня есть большой файл CSV (1,8 ГБ) с тремя столбцами. Каждая строка содержит две строки и числовое значение. Проблема в том, что они дублируются, но меняются местами. Пример:
Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
DEF,ABC,123
Желаемый результат будет выглядеть так:
Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
потому что третья строка содержит ту же информацию, что и первая строка.
РЕДАКТИРОВАТЬ
Данные в основном выглядят так (строки в первых двух столбцах и числовое значение в третьем, 40 миллионов строк):
Примечание. Этот вопрос был задан до того, как OP изменил тег 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})")
Это немного быстрее, но не намного.
Если ваш компьютер имеет несколько ядер, вы можете попробовать использовать многопроцессорность:
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
на любое число, которое вам нравится, помните, что чем выше число, тем выше скорость (не уверен, что действительно большие числа делают наоборот), но выше потребление оперативной памяти.
python
для вашего вопроса, поскольку он делает недействительными все предыдущие ответы.
Можете ли вы справиться с awk:
$ awk -F, '++seen[$3]==1' file
Выход:
COL1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
Explaied:
$ awk -F, ' # set comma as field delimiter
++seen[$3]==1 # count instances of the third field to hash, printing only first
' file
Обновление:
$ awk -F, '++seen[($1<$2?$1 FS $2:$2 FS $1)]==1' file
Выход:
COL1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
Он хэширует каждую встреченную комбинацию первого и второго полей, так что "ABC,DEF"=="DEF,ABC"
и считает их, печатая только первое. ($1<$2?$1 FS $2:$2 FS $1)
: если первое поле меньше второго, то хеш 1st,2nd
- 2nd,1st
хэш 2nd,1st
.
Исходя из описания проблемы, мандат для строки, которая НЕ пропускается, - это когда первое и второе поля в любом порядке при объединении должны быть уникальными. Если так, то ниже awk
поможет
awk -F, '{seen[$1,$2]++;seen[$2,$1]++}seen[$1,$2]==1 && seen[$2,$1]==1' filename
Пример ввода
Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
DEF,ABC,123
GHI,ABC,123
DEF,ABC,123
ABC,GHI,123
DEF,GHI,123
Пример вывода
Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
GHI,ABC,123
DEF,GHI,123
Вы должны сделать некоторую сортировку вашего файла, найти дубликаты будет очень легко.
Если вы хотите использовать саму библиотеку csv: -
Вы можете использовать DictReader и DictWriter.
Import csv
def main():
"""Read csv file, delete duplicates and write it."""
with open('test.csv', 'r',newline='') as inputfile:
with open('testout.csv', 'w', newline='') as outputfile:
duplicatereader = csv.DictReader(inputfile, delimiter=',')
uniquewrite = csv.DictWriter(outputfile, fieldnames=['address', 'floor', 'date', 'price'], delimiter=',')
uniquewrite.writeheader()
keysread = []
for row in duplicatereader:
key = (row['date'], row['price'])
if key not in keysread:
print(row)
keysread.append(key)
uniquewrite.writerow(row)
if __name__ == '__main__':
main()