Присоединение к текстовым файлам с 600M + строки

У меня есть два файла, 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 для этой задачи),

Я вижу два выхода из этого, но не знаю, как реализовать любой из них.

  1. Как я сортирую файлы способом, что команда соединения полагает, что они отсортированы правильно?

  2. Я думал о вычислении 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
7
задан 21.06.2012, 20:01

6 ответов

Я знаю, что это смущающе просто, но это работает.
Основанный на предположении, что мои исходные файлы содержат только символы нижнего регистра, я просто заменил проблематичные апострофы и тире с двумя прописными буквами, обратился, чем присоединенный файлы, наконец возвратил буквы назад к знакам.Именно.

Еще раз спасибо за всех вносящие ответ или проницательный комментарий.

Сортировка взяла как 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
1
ответ дан 07.12.2019, 14:30

IMO лучший способ сделать это должно было бы использовать программирование/язык сценариев, которое Вы знаете лучше всего и:

  1. загрузитесь small.txt в хеш/карту/ассоциативный массив в оперативной памяти включил слова
  2. Обработайте huge.txt линию за линией, добавив столбец, искавший от хеша и пишущий результат в выходной файл
  3. Буферный ввод и вывод так, чтобы это произошло в блоках, по крайней мере, 4K
9
ответ дан 07.12.2019, 14:30

Основываться на ответе 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.

7
ответ дан 07.12.2019, 14:30

Мой ответ подобен Michael Borgwardt, но Вы не должны загружать весь ни один файл в память. Если файлы и отсортированы, то Вы обходите через первый файл одну строку за один раз, и Вы делаете двоичный поиск на втором файле для нахождения целевой рассматриваемой строки. Это - большой доступ HD, но это - низкое потребление памяти.

2
ответ дан 07.12.2019, 14:30

Хорошо, этот подход использует 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(инструмент командной строки), и вызывают его много раз, но порождение нового процесса для миллионов строк немного неэффективно.

Так или иначе имейте это в виду:

  • Каждый .cdb файл не может быть больше, чем 4 ГБ. Таким образом, если необходимо обработать 'small.txt' с размером 10 ГБ, очевидно, необходимо разделить это на несколько файлов и создать 'small1.cdb', 'small2.cdb', 'small3.cbd' и так далее. Это должна быть легкая задача.
  • Вы не должны сортировать 'small.txt', поиск в cdb файле довольно быстр так или иначе.
  • Я не синхронизировал свой небольшой тестовый сценарий здесь, это на основе того, что Вы обеспечили.:)
1
ответ дан 07.12.2019, 14:30

Вместо MySQL Вы могли бы попробовать PostgreSQL, который, вероятно, может справиться с этой задачей более корректно. См. их руководство по эффективному заполнению базы данных.

0
ответ дан 07.12.2019, 14:30

Теги

Похожие вопросы