Remove campos duplicados em uma determinada coluna

5

Gostaria de remover de uma determinada coluna ($ 2 no exemplo) os campos duplicados (separados por vírgulas).

Arquivo de entrada:

A    1,2,3,4   
B    4,5,6,3
C    2,15

Resultado esperado:

A    1,2,3,4
B    5,6
C    15
    
por dovah 21.08.2014 / 11:12

3 respostas

7
perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e'

Você pode executar o acima da seguinte forma:

$ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e' afile 
A    1,2,3,4
B    5,6
C    15

Como funciona

A primeira chamada de perl com -lpe faz as três coisas a seguir.

  • -l[octal] habilita o processamento final da linha, especifica o terminador de linha
  • -p assume loop como -n mas linha de impressão também, como sed
  • -e program uma linha de programa (vários -e é permitido, omitir arquivo de programa)

Isso essencialmente pega o arquivo, retira as novas linhas, opera em uma linha e depois coloca um caractere de nova linha de volta nele quando terminar. Então, é apenas passar pelo arquivo e executar nosso código Perl contra cada um deles.

Quanto ao código Perl real:

  • \s significa um caractere de espaçamento (os cinco caracteres [ \f\n\r\t] e \v nas versões mais recentes de perl , como [[:space:]] ).
  • \K Guarde as coisas restantes de \ K, não inclua em $ &
  • \S+ um ou mais caracteres que não estão no conjunto [\ f \ n \ r \ t \ v]

O join ",", vai obter os resultados e voltar a cada campo para que seja separado por uma vírgula.

O split ",", $& pegará as correspondências encontradas pelo \S+ e as dividirá em apenas os campos, sem a vírgula.

O grep {!$seen{$_}++} pegará o número de cada campo e o adicionará ao hash, $seen{} , onde o número de cada campo será $_ conforme passarmos por cada um deles. Cada vez que um número de campo é "visto", é contado pelo operador ++ , $seen{$_}++ .

O grep{!$seen{$_}++} retornará um valor de campo se for visto apenas uma vez.

Modificado para ver o que está acontecendo

Se você usar essa abominação modificada, você pode ver o que está acontecendo enquanto esse forro de Perl se move pelas linhas do arquivo.

$ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e; @a=keys %seen; @b=values %seen; print "keys: @a | vals: @b"' afile 
keys: 4 1 3 2 | vals: 1 1 1 1
A    1,2,3,4
keys: 6 4 1 3 2 5 | vals: 1 2 1 2 1 1
B    5,6
keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1
C    15

Isso mostra o conteúdo de $seen{} no final do processamento de uma linha do arquivo. Vamos pegar a segunda linha do arquivo.

B    4,5,6,3

E aqui está o que minha versão modificada mostra nessa linha como:

keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1

Então, isso está dizendo que vimos o campo # 6 (1 vez), o campo # 4 (2 vezes), etc. e o campo # 5 (1 vez). Então, quando grep{...} devolve os resultados, ele só retornará resultados desta matriz se estiver presente nesta linha (4,5,6,3) e se tivermos visto apenas 1 vez (6,1,15,5 ). A interseção dessas 2 listas é (5,6) e é isso que é retornado por grep .

Referências

por 27.08.2014 / 11:09
5
{ 
    tr -cs '0-9ABC' '[\n*]' | 
    nl -w1 -s: |
    sort -t: -uk2,2 | sort -t: -k1,1n |
    sed 's/[^:]*://;/^[ABC]/!{H;$!d
        };x;y/\n/,/;s/,/\t/'

} <<\FILE
A       1,2,3,4
B       4,5,6,3
C       2,15
FILE

Basicamente, isso apenas toma algumas medidas para garantir que os dados possam ser executados através de sort -u e ainda assim voltarem de forma relativamente semelhante à que foram lançados.

Primeiro tr transforma qualquer byte em sua entrada que não seja um dígito ou ABC em uma nova linha e aperta qualquer repetição. Então, cada campo recebe sua própria linha.

nl , em seguida, numera cada linha em sua entrada. Sua saída se parece com:

1:A
2:1
3:2

... e assim por diante.

Em seguida, os dados são sort ed - duas vezes. O primeiro sort remove todos os campos duplicados - ele opera somente no segundo campo de dados por linha e separa esses campos com : . O segundo sort restaura o pedido original - a ordem numerada de nl - por sort ing no primeiro campo desta vez.

O último sed reúne tudo de novo. Ele coleta sua entrada, removendo todas as inserções de nl , e e x altera seus buffers de retenção e de padrão para as linhas ABC e $ last. Em cada uma delas, y transforma todas as \n ewlines em , e a última substitui <tab> pela primeira delas por linha.

E os resultados?

OUTPUT

A       1,2,3,4
B       5,6
C       15

E isso, amigos, é o que torna o Unix incrível.

E, como fiquei curioso, aqui está com sed sozinho:

sed 's/\t\(.*\)/,,/;H;$!d;x;:t
     s/,\([^,]*,\)\(.*,\)/,/;tt
     s/,\(\n.\),/\t/g;s/,\(.*\),/\t/'
' <<\FILE
A       1,2,3,4
B       4,5,6,3
C       2,15
FILE

A primeira e a terceira linhas são como preparação / limpeza. O primeiro puxa o arquivo inteiro para o espaço padrão e garante que cada campo seja delimitado por vírgulas, portanto, a primeira linha, por exemplo:

A,1,2,3,4,

parece que depois de passar.

A segunda linha é onde todo o trabalho é feito. Na verdade, é uma função de substituição recursiva - ela substitui continuamente qualquer campo delimitado por vírgulas que pode ser correspondido em outro local apenas a si mesmo até que não haja mais nada a ser encontrado. Esta é a função do comando t est.

A terceira linha, como eu disse, apenas limpa tudo. Ele coloca as vírgulas de volta onde elas pertencem e as guias e etc, e o resultado final é:

OUTPUT

A       1,2,3,4
B       5,6
C       15

Foi divertido, mas fazer isso com uma quantidade considerável de dados é assassinato por computador. É regex recursivo - o pior tipo de recursão, provavelmente. Então, esse é um bom tipo de coisa a se fazer com apenas algumas linhas, mas fazer uma planilha dessa maneira provavelmente não é aconselhável.

    
por 27.08.2014 / 11:43
4

Com awk , é fácil usar um array , split , e um regular loop :

{
    split($2, elements, ",")
    out = ""
    for (i in elements) {
        el = elements[i]
        if (!(el in used)) {
            out = out el ","
        }
        used[el] = 1
    }
    sub(/,$/, "", out)
    $2 = out
}
1

Para cada linha, dividimos a segunda coluna por vírgulas e salvamos os bits em uma matriz elements . Em seguida, construímos o novo valor para essa coluna com um loop, verificando se vimos o valor antes ou não. Estamos mantendo o conjunto de valores que já vimos na matriz (associativa) used . Se el in used , vimos isso antes e não devemos colocá-lo na saída; de outra forma, é novo, e nós o concatenamos para out e o adicionamos ao nosso conjunto de valores vistos para que não o usemos novamente. Finalmente, colocamos a lista construída de volta na segunda coluna. Esta é essencialmente a abordagem que você toma em qualquer outro idioma.

Coloque o código acima em um arquivo e execute-o com awk -f , ou cite tudo como um argumento na linha de comando.

    
por 21.08.2014 / 11:29

Tags