У меня есть два файла, huge.txt
и small.txt
. huge.txt
имеет вокруг 600M строки, и это - 14 ГБ. Каждая строка имеет четыре, располагают разделенные слова с интервалами (маркеры), и наконец другое пространство разделило столбец с числом. small.txt
имеет 150K строки с размером ~3M, пространство разделило слово и число.
Оба файла отсортированы с помощью команды вида без дополнительных опций. Слова в обоих файлах могут включать апострофы (') и тире (-).
Желаемый вывод содержал бы все столбцы от huge.txt
файл и второй столбец (число) от small.txt
где первое слово huge.txt
и первое слово small.txt
соответствие.
Мои попытки ниже потерпевшего полный провал со следующей ошибкой:
cat huge.txt|join -o 1.1 1.2 1.3 1.4 2.2 - small.txt > output.txt
join: memory exhausted
То, что я подозреваю, - то, что порядок сортировки не является правильным так или иначе даже при том, что файлы предварительно отсортированы с помощью:
sort -k1 huge.unsorted.txt > huge.txt
sort -k1 small.unsorted.txt > small.txt
Проблемы, кажется, появляются вокруг слов, которые имеют апострофы (') или тире (-). Я также попробовал сортировку словаря с помощью -d
опция, врезающаяся в ту же ошибку в конце.
Я пытался загрузить файлы в MySQL, создайте индексы и присоединитесь к ним, но это, кажется, занимает недели на моем ноутбуке. (У меня нет компьютера с большей памятью или быстрым диском/SSD для этой задачи),
Я вижу два выхода из этого, но не знаю, как реализовать любой из них.
Как я сортирую файлы способом, что команда соединения полагает, что они отсортированы правильно?
Я думал о вычислении MD5 или некоторых других хешей строк, чтобы избавиться от апострофов и тире, но оставить числа неповрежденными в конце строк. Сделайте сортировку и присоединение с хешами вместо самих строк и наконец "переведите" назад хеши в строки. С тех пор был бы только 150K хешами дело не в этом плохо. Каков был бы хороший способ вычислить отдельные хеши для каждой из строк? Некоторое волшебство AWK?
Посмотрите образцы файла в конце.
Образец huge.txt
had stirred me to 46
had stirred my corruption 57
had stirred old emotions 55
had stirred something in 69
had stirred something within 40
Образец small.txt
caley 114881
calf 2757974
calfed 137861
calfee 71143
calflora 154624
calfskin 148347
calgary 9416465
calgon's 94846
had 987654
Желаемый вывод:
had stirred me to 46 987654
had stirred my corruption 57 987654
had stirred old emotions 55 987654
had stirred something in 69 987654
had stirred something within 40 987654
Я знаю, что это смущающе просто, но это работает.
Основанный на предположении, что мои исходные файлы содержат только символы нижнего регистра, я просто заменил проблематичные апострофы и тире с двумя прописными буквами, обратился, чем присоединенный файлы, наконец возвратил буквы назад к знакам.Именно.
Еще раз спасибо за всех вносящие ответ или проницательный комментарий.
Сортировка взяла как 2 часа для huge.txt (14 ГБ), присоединение к меньше чем часу.
cat small.txt | tr "\'-" "AD" | sort -k1 > small.AD
cat huge.txt | tr "\'-" "AD" | sort -k1 | cat huge.txt | join -o 1.1 1.2 1.3 1.4 2.2 - small.AD | tr "AD" "\'-" > output.txt
IMO лучший способ сделать это должно было бы использовать программирование/язык сценариев, которое Вы знаете лучше всего и:
Основываться на ответе Michael Borgwardt: пока оба файла отсортированы, можно соединить их, в основном выполнив один шаг сортировки с объединением. Это будет немного отличаться, чем стандартная сортировка с объединением, потому что Вы только хотите сохранить один из файлов. Это должно будет, конечно, быть реализовано на Вашем любимом языке программирования.
Вот эскиз алгоритма:
line1 = read a line from file 1
line2 = read a line from file 2
start of loop:
if (first word of line1 == first word of line2) {
write all fields of line1
and second field of line2 to output
line1 = read a line from file 1
go to start of loop
}
else if (first word of line1 < first word of line2) {
write line1 to output
line1 = read a line from file 1
go to start of loop
}
else (first word of line1 > first word of line2) {
line2 = read a line from file 2
go to start of loop
}
Вот версия Python (так как Python, что я, оказывается, знаю лучше всего, не обязательно лучший язык для задания):
file1 = open('file1', 'r')
file2 = open('file2', 'r')
w2, n2 = file2.readline().split()
for line1 in file1:
w11, w12, w13, w14, n15 = line1.split()
if w11 == w2:
print w11, w12, w13, w14, n15, n2
continue
elif w11 < w2:
print w11, w12, w13, w14, n15
continue
else:
while w11 > w2:
w2, n2 = file2.readline().split()
if w11 == w2:
print w11, w12, w13, w14, n15, n2
elif w11 < w2:
print w11, w12, w13, w14, n15
и для полноты, после того, как некоторое рытье здесь - то, что я придумал для Awk:
BEGIN {
getline line2 <"file2";
split(line2, a);
}
{
if (a[1] == $1) print $0,a[2];
else if (a[1] < $1) print $0;
else { getline line2 <"file2"; split(line2, a); }
}
Вызовите как awk -f program.awk <file1
.
Мой ответ подобен Michael Borgwardt, но Вы не должны загружать весь ни один файл в память. Если файлы и отсортированы, то Вы обходите через первый файл одну строку за один раз, и Вы делаете двоичный поиск на втором файле для нахождения целевой рассматриваемой строки. Это - большой доступ HD, но это - низкое потребление памяти.
Хорошо, этот подход использует http://cr.yp.to/cdb.html в качестве более быстрого способа искать содержание 'small.txt':
cdbmake
(часть 'freecdb' пакета в Ubuntu, но существует много доступных реализаций.Используйте awk для передачи по каналу small.txt к cdbmake
.
% awk ' { printf "+%d,%d:%s->%s\n", \
length($1),length($2),$1,$2 } \
END { print "" }' | cdbmake small.cdb small.cdbtmp
(Это преобразовывает строку 'small.txt' от чего-то как "значение ключа" в "+ks, vs:key-> значение".)
Теперь Вы идете линию за линией по 'huge.txt' и распечатываете его, ища первое слово в 'small.cdb':
#!/bin/python
import cdb
import fileinput
c = cdb.init("small.cdb")
for l in fileinput.input(['huge.txt']):
print l.strip(),
v = c.get(l.split()[0])
print "" if v == None else v
Необходимо было бы установить python-cdb, конечно, чтобы заставить этот крошечный отрывок работать (и он работает только на Python 2.5 из-за 'условного выражения'. Так или иначе существует большая привязка для любого языка, который Вы любите. Вы могли также использовать cdbget
(инструмент командной строки), и вызывают его много раз, но порождение нового процесса для миллионов строк немного неэффективно.
Так или иначе имейте это в виду:
Вместо MySQL Вы могли бы попробовать PostgreSQL, который, вероятно, может справиться с этой задачей более корректно. См. их руководство по эффективному заполнению базы данных.