Unindo arquivos de texto com linhas de 600M +

7

Eu tenho dois arquivos, huge.txt e small.txt . huge.txt tem cerca de 600 milhões de linhas e 14 GB. Cada linha tem quatro palavras separadas por espaço (tokens) e finalmente outra coluna separada por espaço com um número. small.txt tem 150 mil linhas com um tamanho de ~ 3M, uma palavra separada por espaço e um número.

Ambos os arquivos são classificados usando o comando sort, sem opções extras. As palavras nos dois arquivos podem incluir apóstrofos (') e traços (-).

A saída desejada deve conter todas as colunas do arquivo huge.txt e a segunda coluna (o número) de small.txt , onde a primeira palavra de huge.txt e a primeira palavra de small.txt correspondem.

Minhas tentativas abaixo falharam miseravelmente com o seguinte erro:

cat huge.txt|join -o 1.1 1.2 1.3 1.4 2.2 - small.txt > output.txt

join: memory exhausted  

O que eu suspeito é que a ordem de classificação não está correta de alguma forma, mesmo que os arquivos sejam pré-classificados usando:

sort -k1 huge.unsorted.txt > huge.txt
sort -k1 small.unsorted.txt > small.txt

Os problemas parecem aparecer em torno de palavras que têm apóstrofos (') ou traços (-). Eu também tentei classificar o dicionário usando a opção -d esbarrando no mesmo erro no final.

Eu tentei carregar os arquivos no MySQL, criar índices e juntá-los, mas parece demorar semanas no meu laptop. (Eu não tenho um computador com mais memória ou disco rápido / SSD para esta tarefa)

Eu vejo duas maneiras de sair disso, mas não sei como implementar nenhuma delas.

  1. Como classifico os arquivos de forma que o comando de associação os considere classificados corretamente?

  2. Eu estava pensando em calcular MD5 ou alguns outros hashes das seqüências para se livrar dos apóstrofos e traços, mas deixar os números intactos no final das linhas. Faça a classificação e junte-se aos hashes em vez das próprias cordas e finalmente "traduza" de volta os hashes para as cordas. Como haveria apenas 150 mil hashes, não é tão ruim assim. Qual seria uma boa maneira de calcular hashes individuais para cada uma das strings? Alguma magia AWK?

Veja amostras de arquivos no final.

Exemplo de enorme.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 

Exemplo de small.txt

caley 114881 
calf 2757974 
calfed 137861 
calfee 71143 
calflora 154624 
calfskin 148347 
calgary 9416465 
calgon's 94846 
had 987654

Saída desejada:

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
    
por dnkb 26.05.2010 / 21:29

6 respostas

1

Eu sei que é embaraçosamente simples, mas funciona.
Com base no pressuposto de que meus arquivos originais contêm apenas caracteres minúsculos, eu simplesmente substituí os apóstrofos e traços problemáticos por duas letras maiúsculas, reordenadas do que juntadas aos arquivos, e finalmente alterei as letras de volta para os sinais. É isso aí.

Obrigado novamente por todos que contribuíram com uma resposta ou um comentário perspicaz.

A classificação demorou 2 horas para o arquivo enorme.txt (14Gig), com menos de uma hora de duração.

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
    
por 01.06.2010 / 04:23
9

IMO A melhor maneira de fazer isso seria usar a linguagem de programação / script que você conhece melhor e:

  1. carrega o arquivo small.txt em uma matriz hash / mapa / associativa na memória digitada nas palavras
  2. Processo enorme.txt linha por linha, adicionando a coluna pesquisada do hash e gravando o resultado em um arquivo de saída
  3. Buffer de entrada e saída para que isso aconteça em partes de pelo menos 4K
por 26.05.2010 / 21:43
7

Para construir a resposta de Michael Borgwardt: contanto que ambos os arquivos sejam classificados, você pode colocá-los juntos basicamente executando uma etapa de um mergesort. Será um pouco diferente do padrão do mergesort porque você só quer manter um dos arquivos. Isso, obviamente, terá que ser implementado em sua linguagem de programação favorita.

Aqui está um esboço do algoritmo:

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
}

Aqui está uma versão em Python (já que Python é o que eu sei melhor, não necessariamente a melhor linguagem para o trabalho):

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

e por completo, depois de algumas pesquisas, aqui está o que eu criei para o 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); }
}

Invoque como awk -f program.awk <file1 .

    
por 27.05.2010 / 01:09
2

Minha resposta é semelhante à de Michael Borgwardt, mas você não precisa carregar todos os arquivos na memória. Se os arquivos forem classificados, você percorre o primeiro arquivo, uma linha por vez, e faz uma pesquisa binária no segundo arquivo para encontrar a linha de destino em questão. Isso é muito acesso HD, mas é baixo consumo de memória.

    
por 27.05.2010 / 00:41
1

OK, essa abordagem usa o link como uma maneira mais rápida de pesquisar o conteúdo de 'small.txt':

  • Vá e instale cdbmake (parte do pacote 'freecdb' no Ubuntu, mas há muitas implementações disponíveis.
  • Use o awk para canalizar o arquivo small.txt para cdbmake .

    % awk '    { printf "+%d,%d:%s->%s\n", \
                    length($1),length($2),$1,$2 } \
           END { print "" }' | cdbmake small.cdb small.cdbtmp
    

(Isso transforma uma linha de 'small.txt' de algo como "key value" em "+ ks, vs: valor key- >").

  • Agora você vai linha por linha em 'enorme.txt' e imprime, procurando a primeira palavra em '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
    

Você teria que instalar o python-cdb, é claro, para fazer esse pequeno trecho de código funcionar (e ele funciona apenas para o Python 2.5, por causa do ' expressão condicional ". De qualquer forma, há muitas ligações para qualquer linguagem que você goste. Você também pode usar cdbget (uma ferramenta de linha de comando) e invocá-la repetidas vezes. novamente, mas gerar um novo processo para milhões de linhas é um pouco ineficaz.

De qualquer forma, tenha isso em mente:

  • Cada arquivo .cdb não pode ser maior que 4 GB. Portanto, se você tiver que processar 'small.txt' com um tamanho de 10 GB, obviamente terá que dividir isso em vários arquivos e criar 'small1.cdb', 'small2.cdb', 'small3.cbd' e assim por diante. Deve ser uma tarefa fácil.
  • Você não precisa classificar 'small.txt', uma pesquisa em um arquivo cdb é bem rápida de qualquer maneira.
  • Não cronometrei meu pequeno caso de teste aqui, é baseado no que você forneceu. :)
por 27.05.2010 / 23:15
0

Em vez do MySQL, você pode tentar o PostgreSQL, que provavelmente pode lidar com essa tarefa mais facilmente. Consulte o seu guia sobre o preenchimento eficiente de um banco de dados.

    
por 26.05.2010 / 23:49