Объединение 2 очень больших текстовых файлов, обновление каждой строки без использования памяти - PullRequest
6 голосов
/ 30 августа 2011

Скажем, у меня есть 2 текстовых файла по 2 миллиона строк в каждом (размер файла ~ 50-80 МБ каждый). Структура обоих файлов одинакова:

Column1 Column2 Column3
...

Столбец 1 никогда не изменяется, Столбец 2 : одно и то же значение может отсутствовать в обоих файлах и не будет в одинаковом порядке для обоих файлов, Столбец3 - это число, которое будет отличаться в каждом файле.

Мне нужно иметь возможность объединить их оба в один файл, соответствующий столбцу 2. Если Column2 существует в обоих файлах, обновите Column3, добавив значения Column3 из обоих файлов вместе.

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

Есть ли способ сделать это без загрузки каждой строки в память? Я в основном знаком с PHP, но открыт для сценариев Python, Java или Shell, если они не слишком сложны для понимания.

Ответы [ 5 ]

2 голосов
/ 30 августа 2011

Я бы пошел с командной строкой sort(1), чтобы объединить и отсортировать файлы. После этого должен быть простой скрипт для вычисления сумм. Я не знаю PHP, поэтому приведу пример на python:

sort -k2 <file1> <file2> | python -c "
  import itertools,sys
  allLines = (x.strip().split(' ') for x in sys.stdin)
  groups = itertools.groupby(allLines, lambda x:x[1])
  for k,lines in groups:
      firstLine = iter(g).next()
      print firstLine[0], firstline[1], sum(int(x[2]) for x in lines)
"
1 голос
/ 30 августа 2011

Вы можете легко решить эту проблему с помощью модуля Python sqlite3 без использования большого количества памяти (около 13 МБ с 1 миллионом строк):

import sqlite3

files = ("f1.txt", "f2.txt")    # Files to compare

# # Create test data
# for file_ in files:
#   f = open(file_, "w")
#   fld2 = 0
#   for fld1 in "abc def ghi jkl".split():
#       for fld3 in range(1000000 / 4):
#           fld2 += 1
#           f.write("%s %s %s\n" % (fld1, fld2, 1))
# 
#   f.close()

sqlite_file = "./join.tmp"      # or :memory: if you don't want to create a file

cnx = sqlite3.connect(sqlite_file)

for file_ in range(len(files)):     # Create & load tables
    table = "file%d" % (file_+1)
    cnx.execute("drop table if exists %s" % table)
    cnx.execute("create table %s (fld1 text, fld2 int primary key, fld3 int)" % table)

    for line in open(files[file_], "r"):
        cnx.execute("insert into %s values (?,?,?)" % table, line.split())

# Join & result
cur = cnx.execute("select f1.fld1, f1.fld2, (f1.fld3+f2.fld3) from file1 f1 join file2 f2 on f1.fld2==f2.fld2")
while True:
    row = cur.fetchone()
    if not row:
        break

    print row[0], row[1], row[2]

cnx.close()
1 голос
/ 30 августа 2011

Что может бросить вас, так это то, что вы смотрите на два файла.В этом нет необходимости.Чтобы использовать превосходный пример Марка: file1:

abc 12 34
abc 56 78
abc 90 12

file2:

abc 90 87  
abc 12 67  
abc 23 1  

затем

sort file1 file2 > file3

дает файл3:

abc 12 34
abc 12 67  
abc 23 1
abc 56 78
abc 90 12
abc 90 87  

Вторая неделя CS-101, чтобы уменьшить его до окончательного вида.

1 голос
/ 30 августа 2011

Хорошо, так что, если я правильно читаю, у вас будет:

file1:

abc 12 34
abc 56 78
abc 90 12

file2:

abc 90 87  <-- common column 2
abc 12 67  <---common column 2
abc 23 1   <-- unique column 2

вывод должен быть:

abc 12 101
abc 90 99

Если это так, то что-то вроде этого (при условии, что они отформатированы в .csv):

$f1 = fopen('file1.txt', 'rb');
$f2 = fopen('file2.txt', 'rb');
$fout = fopen('outputxt.');

$data = array();
while(1) {
    if (feof($line1) || feof($line2)) {
        break; // quit if we hit the end of either file
    }

    $line1 = fgetcsv($f1);
    if (isset($data[$line1[1]])) {
       // saw the col2 value earlier, so do the math for the output file:
       $col3 = $line1[2] + $data[$line1[1]];
       $output = array($line[0], $line1[1], $col3);
       fputcsv($fout, $output);
       unset($data[$line1[1]]);
    } else {
       $data[$line1[1]] = $line1; // cache the line, if the col2 value wasn't seen already
    }

    $line2 = fgetcsv($f2);
    if (isset($data[$line2[1]])) {
       $col3 = $data[$line2[1]] + $line2[2];
       $newdata = array($line2[0], $line2[1], $col3);
       fputcsv($fout, $newdata);
       unset($data[$line2[1]]); // remove line from cache
    } else {
       $data[$line2[1]] = $line2;
    }
}

fclose($f1);
fclose($f2);
fclose($fout);

Это происходит с моей головы, не проверено, вероятно, не будет работать, YMMV и т. Д. *

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

0 голосов
/ 30 августа 2011

PHP memory_limit подходит для основной задачи сценариев веб-сервера. Он совершенно не подходит для пакетной обработки данных, например, для работы, которую вы пытаетесь выполнить. Проблема в том, что PHP настроил memory_limit, а не в том, что вы пытаетесь сделать что-то, что требует «слишком много» памяти. Мой телефон имеет достаточно памяти, чтобы просто загрузить 2 80 МБ файла в память и сделать это быстрым / простым способом, не говоря уже о реальном компьютере, который должен загружать гигабайты (или не менее 1 ГБ). данных без пота.

Очевидно, вы можете установить PHP memory_limit (который является произвольным и очень маленьким по сегодняшним стандартам) во время выполнения с ini_set, только для этого сценария. Вы знаете, сколько памяти у вас на самом деле доступно на сервере? Я знаю, что многие провайдеры виртуального хостинга предоставляют вам очень небольшие объемы памяти по современным стандартам, потому что они не ожидают, что вы будете делать гораздо больше, чем обработка запросов веб-страниц. Но вы, вероятно, можете просто сделать это прямо в PHP так, как вы хотите, не перепрыгивая через обручи (и значительно замедляя процесс), чтобы попытаться избежать загрузки всех файлов в память сразу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...