Mesclar várias linhas no mesmo arquivo com base na coluna 1

0

Eu ainda estou aprendendo programação e eu tentei muitas coisas, mas não consegui o formato correto. Eu tenho um arquivo delimitado por guias com 17 colunas e muitas (cerca de 50.000) linhas. O arquivo já está classificado pela primeira coluna. Eu quero mesclar linhas que têm a mesma primeira coluna (A), mas todas as outras 16 colunas são diferentes e eu quero manter todas as informações em uma linha, de preferência na mesma coluna com ponto e vírgula; como um delimitador entre eles. Eu quero manter a guia como um delimitador no arquivo de saída. Muito obrigado pelas respostas e se você também pudesse explicar a resposta onde eu errei, seria ainda melhor:).

Eu já tentei até agora:

awk -F'\t' 'NF>1{a[$1] = a[$1]";"$2}END{for(i in a){print i""a[i]}}' filename.txt

perl -F',' -anle 'next if /^$/;$h{$F[0]} = $h{$F[0]}.", ".$F[1];
END{print $_,$h{$_},"\n" for sort keys %h}' filename.txt

FORMATO DE ARQUIVO (outras 15 colunas têm o mesmo formato da coluna B)

A     B     C    
123   fvv   ggg
123   kjf   ggg
123   ccd   att
567   abc   gst
567   abc   hgt
879   ttt   tyt

A saída que eu quero (eu preciso de todas as 17 colunas e para as colunas 2-16 eu preciso da mesma saída que na coluna B e C). Todos os casos de B devem estar em B e todos os casos de C devem estar em C e todos os casos de D devem estar em D etc. Assim, a saída tem 17 colunas exatamente como a entrada e, em vez de 50.000 linhas, deve ter cerca de 20.000 , porque há muitas repetições para a coluna 1 (para este arquivo específico):

A     B                C
123   fvv;kjf;ccd      ggg;ggg;att
567   abc;abc          gst;hgt
879   ttt              lll
    
por Fluorine 18.02.2016 / 12:52

6 respostas

1

awk '{
      if(NR!=1){a[$1]=$2";"a[$1]}
      else print $0}
    END{
      n = asorti(a, b);
      for (n in b) {
      print b[n],a[b[n]]
      }
    }'
    
por 18.02.2016 / 14:09
0

Uma solução perl:

$ perl -F"\t" -anle 'if($.==1){print; next} push @{$k{$F[0]}},@F[1..$#F]; 
  END{print "$_\t" . join(";",@{$k{$_}}) for sort keys(%k)}' file 
A   B   
123 fvv;kjf;ccd
567 abc;abc
879 ttt

Isso pode funcionar em um número arbitrário de campos. No entanto, é necessário carregar algumas coisas na memória e isso pode ser um problema se o arquivo for grande.

Quanto a onde você errou, não podemos dizer a menos que você explique o que realmente aconteceu, mas, acima de tudo, sua tentativa de perl falhará porque:

  • Você está usando -F, , que define o separador de campos para uma vírgula quando sua entrada possui guias.
  • Você está usando -l e print "foo\n" . O -l já adiciona uma nova linha a cada chamada impressa, portanto você terá várias linhas em branco.
  • Você está usando $h{$F[0]}.", ".$F[1]; para anexar, então, a primeira vez que é executado e $h{$F[0]} não está definido, você adicionará um , extra no início de seu valor armazenado.
  • Você está apenas olhando para o segundo campo, ignorando todos os outros.

Da mesma forma, seu awk falhará porque:

  • Você está imprimindo foo""bar , que concatenará a saída sem espaço entre cada campo. Você quer print foo,bar e também deseja OFS="\t" para saída separada por tabulações.
  • Você está apenas olhando para o segundo campo, ignorando todos os outros.
por 18.02.2016 / 14:18
0

desculpas por este one-liner, mas aqui vai -

awk 'BEGIN{FS="\t"} {for(i=2; i<=NF; i++) { if (!a[$1]) a[$1]=$1FS$i ;else a[$1]=a[$1]";"$i};if ($1 != old) b[j++] = a[old];old=$1 } END{for (i=0; i<j; i++) print b[i] }' 1

123 fvv ;kjf;ccd
567 abc;abc
879 ttt
    
por 18.02.2016 / 14:27
0

Perl pode fazer isso com facilidade, usando um hash:

#!/usr/bin/env perl

use strict;
use warnings;

my %stuff;
my @header = split ' ', <>;

#read in the data to "stuff"
while ( <> ) { 
   my ( $key, $value ) = split; 
   push ( @{$stuff{$key}}, $value ); 
}

print join ("\t", @header ),"\n"; 
foreach my $key ( sort keys %stuff ) {
   print $key, "\t", join ";", @{$stuff{$key}},"\n";
}

Saída:

A   B
123 fvv;kjf;ccd;
567 abc;abc;
879 ttt;

Onde você errou? Honestamente, eu sugeriria tentar compactar tudo em um único forro. Isso é - na minha opinião - uma prática muito ruim. Na melhor das hipóteses, promove códigos inescrutáveis que são difíceis de seguir.

O acima pode ser condensado, mas realmente vale a pena fazer isso em primeiro lugar.

Para suportar várias colunas, você começa a causar um pequeno incômodo com as larguras das colunas.

Isso funciona, mas produz resultados em que o recuo não está alinhado corretamente:

#!/usr/bin/env perl

use strict;
use warnings;

my %stuff; 

my ( $id, @header ) = split ' ', <>;

while ( <> ) { 
   my ( $key, @values ) = split; 
   my %row;
   @row{@header} = @values; 
   push ( @{$stuff{$key}{$_}}, $row{$_} ) for keys %row;
}

print join ( "\t", $id, @header),"\n";
foreach my $key ( sort keys %stuff ) {
   print join ("\t", $key, map { join ";", @{$stuff{$key}{$_}}} @header), "\n";
}

Resultado de:

A   B   C
123 fvv;kjf;ccd ggg;ggg;att
567 abc;abc gst;hgt
879 ttt tyt

Se a separação de separadores não for adequada às suas necessidades, pode utilizar sprintf para efetuar a formatação:

my $format = '%12s';
print map { sprintf($format, $_) } ( $id, @header),"\n";
foreach my $key ( sort keys %stuff ) {   
   print map { sprintf($format, $_) } ( $key, map { join ";", @{$stuff{$key}{$_}}} @header),"\n";
}

Fazemos algum uso de map aqui, o que eu aprecio não é exatamente uma coisa óbvia.

O que ele faz é pegar uma lista e aplicar uma transformação a cada elemento. Então - no exemplo acima:

print join ("\y", map { join ";", @$_ } ([1,2,3],[4,5,6],[7,8,9]) )

gerará:

1;2;3   4;5;6   7;8;9

A operação map está dizendo 'junte cada subarray'; e, em seguida, retornar isso como uma lista ... que podemos então juntar a uma aba. Isso é basicamente o que o acima está fazendo.

    
por 18.02.2016 / 14:10
0
awk '
    function p(n,A){
        s = n
        for(i=2;i<=NF;i++){
            s = s "\t" A[i]
            A[i] = $i
        }
        if(n)
            print s
    }
    NR==1{
        print
        next
    }
    $1==n{
        for(i=2;i<=NR;i++)
            A[i] = A[i] ";" $i
        next
    }
    {
        p(n,A)
        n = $1
    }
    END{
        p(n,A)
    }
    ' file
    
por 18.02.2016 / 19:14
0

Eu quero mesclar linhas que têm a mesma primeira coluna (A), mas todas as outras 16 colunas são diferentes e eu quero manter todas as informações em uma linha

Você recebeu algumas respostas, mas quero salientar que o que você descreveu nessas 30 palavras é conhecido no SQL como join . Se você importar suas 50.000 linhas em duas tabelas no SQLite, você obtém o efeito desejado com

select * from R join S on r1 = s1

onde r1 e s1 são nomes de coluna que você determina.

Existem muitas vantagens em usar SQL para algo assim, especialmente quando os critérios de junção e seleção se tornam mais complexos. É uma das razões pelas quais o SQL foi inventado.

    
por 18.02.2016 / 22:11