Comando groupwise uniq?

7

Estou procurando um comando para obter de um arquivo neste formato:

hello 32
hello 67
hi    2
ho    1212
ho    1390
ho    3000

Para este formato (desduplique tomando a última linha de um "grupo"):

hello 67
hi    2
ho    3000

No momento, estou usando um snippet Python e pandas:

    df = pd.read_csv(self.input().path, sep='\t', names=('id', 'val'))

    # how to replace this logic with shell commands?
    surface = df.drop_duplicates(cols=('id'), take_last=True)

    with self.output().open('w') as output:
        surface.to_csv(output, sep='\t', cols=('id', 'val'))

Atualização: Obrigado pelas ótimas respostas. Aqui estão alguns benchmarks:

O arquivo de entrada é 246M e contém 8583313 linhas. Ordem não importa. A primeira coluna tem um tamanho fixo de 9 caracteres.

Exemplo do arquivo de entrada:

000000027       20131017023259.0        00
000000027       20131017023259.0        11
000000035       20130827104320.0        01
000000035       20130827104320.0        04
000000043       20120127083412.0        01
...
                              time        space complexity

tac .. | sort -k1,1 -u        27.43682s   O(log(n))
Python/Pandas                 11.76063s   O(n)
awk '{c[$1]=$0;} END{for(...  11.72060s   O(n)

Como a primeira coluna tem um tamanho fixo, uniq -w também pode ser usado:

tac {input} | uniq -w 9        3.25484s   O(1)
    
por miku 19.06.2014 / 18:09

4 respostas

5

Isso parece loucura, e esperamos que haja uma maneira melhor, mas:

tac foo | sort -k 1,1 -u

tac é usado para reverter o arquivo, para que você obtenha o último e não o primeiro.

-k 1,1 diz usar apenas o primeiro campo para comparação.

-u torna-o único.

    
por 19.06.2014 / 18:16
4

Se você não se importa com a ordem de saída, aqui está uma awk solution:

$ awk '
    {a[$1] = !a[$1] ? $2 : a[$1] < $2 ? $2 : a[$1]}
    END {
        for (i in a) { print i,a[i] }
    }
' file
hi 2
hello 67
ho 3000
    
por 19.06.2014 / 18:26
3

Mais algumas opções:

  1. perl , se você não se importa com a ordem das linhas.

    perl -lane '$k{$F[0]}=$F[1]; END{print "$_ $k{$_}" for keys(%k)}' file
    
  2. Um awk mais simples

    awk '{c[$1]=$0;} END{for(i in c){print c[i]}}' file
    
  3. Um escudo bobo

    while read a b; do grep -w ^"$a" file | tail -n1 ; done < file | uniq
    
por 19.06.2014 / 18:41
0

Bem, você pode fazer isso com sort

sort -u -k1,1 test

EDIT: tac é a solução

    
por 19.06.2014 / 18:15