Объедините гигабайты текста в один файл, отсортированный по количеству вхождений
Моя цель для этого сценария - взять папку, полную текстовых файлов, захватить каждую строку во всех файлах, а затем вывести один файл, содержащий каждую уникальную строку в порядке убывания частоты.
Он не просто находит уникальные строки, он обнаруживает, как часто каждая уникальная строка появляется во всех файлах.
С этим скриптом нужно обрабатывать МНОГО текста - как минимум около 2 ГБ, поэтому мне нужно сделать это эффективно. До сих пор я не достиг этой цели.
import os, sys #needed for looking into a directory
from sys import argv #allows passing of arguments from command line, where I call the script
from collections import Counter #allows the lists to be sorted by number of occurrences
#Pass argument containing Directory of files to be combined
dir_string = str((argv[1]))
filenames=[]
#Get name of files in directory, add them to a list
for file in os.listdir(dir_string):
if file.endswith(".txt"):
filenames.append(os.path.join(dir_string, file)) #add names of files to a list
#Declare name of file to be written
out_file_name = dir_string+".txt"
#Create output file
outfile = open(out_file_name, "w")
#Declare list to be filled with lines seen
lines_seen = []
#Parse All Lines in all files
for fname in filenames: #for all files in list
with open(fname) as infile: #open a given file
for line in infile: #for all lines in current file, read one by one
#Here's the problem.
lines_seen.append(str(line).strip('\n')) #add line to list of lines seen,
#removing the endline
#Organizes the list by number of occurences, but produced a list that contains
# [(item a, # of a occurrences ), (item b, # of b occurrences)...]
lines_seen = Counter(lines_seen).most_common()
#Write file line by line to the output file
for item in lines_seen: outfile.write(str(item[0])+"\n")
outfile.close()
Когда я получаю сообщение об ошибке, речь идет о строке lines_seen.append(str(line).strip('\n'))
,
Сначала я попытался добавить строки без преобразования в строку и зачистки, но в строку включился бы видимый '\n', что для меня неприемлемо. Для небольших списков преобразование в строку и разборку не слишком обременительно для памяти. Я не мог найти более эффективный способ избавиться от конечного персонажа
На моем ПК это вызывает MemoryError
на моем Mac это дает мне Killed: 9
- еще не пробовал в Linux.
Нужно ли конвертировать в двоичный файл, собрать свой упорядоченный список, а затем преобразовать обратно? Как еще это можно сделать?
РЕДАКТИРОВАТЬ - стало ясно, что лучший способ сделать это с помощью команд Unix
cd DirectoryWithFiles
cat *.txt | sort | uniq -c | sort -n -r > wordlist_with_count.txt
cut -c6- wordlist_with_count.txt > wordlist_sorted.txt
3 ответа
Это подход к снижению потребления памяти, который я предлагал в комментариях под одним из других ответов:
lines_seen = collections.Counter()
for filename in filenames:
with open(filename, 'r') as file:
for line in file:
line = line.strip('\n')
if line:
lines_seen.update([line])
with open(out_file_name, "w") as outfile:
for line, count in lines_seen.most_common():
outfile.write('{}, {}\n'.format(line, count))
Обратите внимание, что line.strip('\n')
удаление только новой строки в конце каждой прочитанной строки, поэтому line.rstrip('\n')
будет более эффективным. Вы также можете удалить начальные и конечные пробелы, используя line.strip()
, Избавление от, возможно, значительного, свободного пространства, хранящегося, еще больше уменьшит использование памяти.
Я бы решил эту проблему вот так
import os, sys #needed for looking into a directory
from sys import argv #allows passing of arguments from command line, where I call the script
from collections import Counter #allows the lists to be sorted by number of occurrences
#Pass argument containing Directory of files to be combined
dir_string = str((argv[1]))
#Get name of files in directory, add them to a list
filenames = []
for file in os.listdir(dir_string):
if file.endswith(".txt"):
filenames.append(os.path.join(dir_string, file)) #add names of files to a list
#Declare name of file to be written
out_file_name = os.path.join(dir_string, 'out.txt')
# write all the files to a single file instead of list
with open(out_file_name, "w") as outfile:
for fname in filenames: #for all files in list
with open(fname) as infile: #open a given file
for line in infile: #for all lines in current file, read one by one
outfile.write(line)
# create a counter object from outfile
with open(out_file_name, "r") as outfile:
c = Counter(outfile)
print "sorted by line alphabhitically"
from operator import itemgetter
print sorted(c.items(),key=itemgetter(0))
print "sorted by count"
print sorted(c.items(), key=itemgetter(1))
def index_in_file(unique_line):
with open(out_file_name, "r") as outfile:
for num, line in enumerate(outfile, 1):
if unique_line[0] in line:
return num
print "sorted by apperance of line in the outfile"
s= sorted(c.items(),key=index_in_file)
print s
# Once you decide what kind of sort you want, write the sorted elements into a outfile.
with open(out_file_name, "w") as outfile:
for ss in s:
outfile.write(ss[0].rstrip()+':'+str(ss[1])+'\n')
Ваша проблема, очевидно, нехватка памяти.
Вы можете устранить лишние строки в lines_seen во время процесса, это может помочь.
from collections import Counter
lines_seen = Counter()
# in the for loop :
lines_seen[ lines_seen.append(str(line).strip('\n')) ] += 1
# at the end:
for item in lines_seen.most_common():
outfile.write(str(item[0])+"\n")
РЕДАКТИРОВАТЬ
Другое решение будет, как указано в комментариях:
from collections import Counter
lines_seen = Counter()
# get the files names
for fname in filenames: #for all files in list
with open(fname) as infile: #open a given file
lines_seen.update(infile.read().split('\n'))
for item in lines_seen.most_common():
print( item[0], file=outfile )