Usando o awk para identificar o número de colunas idênticas

6

Eu tenho um grande número de arquivos individuais que contêm seis colunas cada (o número de linhas pode variar). Como um exemplo simples:

1   0   0   0   0   0

0   1   1   1   0   0

Estou tentando identificar quantas colunas exclusivas eu tenho (ou seja, números e sua correspondência de pedido), nesse caso, seriam 3.

Existe uma linha simples para fazer isso? Eu sei que é fácil comparar uma coluna com outra coluna, mas como encontrar colunas idênticas?

    
por Sarah 13.10.2014 / 20:32

5 respostas

8

Você pode contar as colunas exclusivas com o seguinte canal:

$ awk '{for (i=1; i<=NF; ++i) a[i]=a[i]$i; } END { for (i in a) print a[i] }' foo \
  | sort -u | wc -l

O comando awk transpõe sua entrada, as linhas resultantes são ordenadas, somente linhas únicas são mantidas ( -u ) e no final todas as linhas (exclusivas) (ou seja, as colunas transpostas) são contadas ( wc -l ). / p>

Observe que NF é uma variável interna do awk e é automaticamente definida para o número de campos no registro atual. $i faz referência ao campo i-ésimo e END protege o bloco a seguir, de forma que ele seja executado depois que todos os registros forem processados. O Awk usa por padrão a delimitação de campos em branco e não em branco.

    
por 13.10.2014 / 21:14
3

(((...))), but how to find identical columns?

$ printf '%s\n' '1 0 0 0 0 0' '0 1 1 1 0 0' | awk -vSUBSEP='=' '
    { for (i=1; i<NF; i++)
        for (j=i+1; j<=NF; j++)
          if ($i==$j)
            M[i,j]++
    }
    END{ for (m in M) if (M[m]==NR) print m }'
5=6
2=3
2=4
3=4

Para todas as colunas i<j de cada linha, incremente M[i,j] sempre que os valores dessas colunas forem iguais. Portanto, M[i,j]==NR depois de ler NR rows significa que os valores eram idênticos para todas as linhas lidas.

    
por 13.10.2014 / 21:22
2

Essa pergunta me interessou e eu queria seguir uma abordagem que não consegui descobrir exatamente e obtenha uma ajuda maravilhosa depois que eu postei como uma questão diferente . Você poderia entender a abordagem que estou tentando seguir a partir da pergunta que publiquei.

Embora você tenha especificado awk , espero que isso ainda seja útil para alguém com um problema semelhante.

Eu tenho mais duas soluções para esse problema (uma da resposta do Gnouc que é um perl e outra de John's solução combinada com a minha solução).

Isso é o que eu tenho e espero que isso ajude.

#The variable appended_input will remove spaces/tabs and just append the rows. 
#Modify the file name in this line. Here I use inputfile as the filename. 

appended_input=$(column -s '\t' inputfile | tr -d '[:space:]') ;

#The array variable will store each column-wise value as an array element.  
#I use sort to find the number of unique elements.

array=($(
    for ((i=0; i<6; i++))
    do
        new=${appended_input:$i:1}
        for ((j=i+6; j<${#appended_input}; j=j+6))
        do 
            new="$new${appended_input:$j:1}"
        done
        echo "$new"
    done
    )) | echo "${array[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '

Teste

Meu arquivo de entrada é como abaixo.

1 0 0 1 0 0
0 1 1 0 0 0
1 1 1 1 1 0
1 0 0 1 0 1
1 0 0 1 0 1

Depois de executar o script acima, recebo a saída como

00011 00100 01100 10111

Você poderia ter um wc -w como um pipe final e obteria a saída como apenas 4, em vez dos valores de coluna exclusivos, conforme acima.

    
por 14.10.2014 / 07:46
1

Aqui está um pequeno script de sed que eu realmente escrevi para mim não há muito tempo. Eu só me diverti um pouco atualizando, no entanto. Ele faz todo o trabalho por si só:

cdup() { _u= _d= 
         case "${1#-}" in (U) _u='\)\(';; (D) _d='\
';      _d="$_d\2$_d";; (*) ! :;;esac && shift
sed 's/  */  /g;H;1h;1d;x;:t
     s/   *\(.*\(\n\)\)\([^ ]\{1,\}\) */ /;tt
     s/ /  /g;h;$!d;s/.*/  &  /;:n
     /\( \([^ ]\{1,\}\) \)\(.*'"$_u${_d:+.*}\)/{ 
         s//${_d:- }"'/;s/$\n*//;tn
};   s/.* \n\n*//;s/  *//;s// /g
     s/\n\n/ /g;y/ \n/\n /' "$@"                        
     unset -v _u _d
}  

sed trabalha duas linhas de cada vez ao reorganizar os campos em sua entrada para alinhar por coluna - e empilha seu trabalho no buffer de armazenamento entre cada linha. Ele não delimita nada além do delimitador de espaço original em seu exemplo (e eu originalmente o escrevi para manipular $IFS arrays separados por arg) - e assim, desde que o delimitador seja sólido, campos de qualquer tamanho razoável contendo mais qualquer caractere, mas o delimitador deve funcionar também.

Por isso, faz (L1COL1\nL2COL1) (L1COL2\nL2COL2)...((L[12]C1)\nL3COL1)... pelo tempo que for necessário até encontrar a última linha. No momento em que isso acontece, todos os dados da memória são organizados de forma tão precisa que é uma tarefa trivial verificar se há duplicatas - e, portanto, só imprime colunas uma vez, independentemente de quantas vezes elas aparecem na entrada:

cdup <<\COLS
1 A 4 Z 1
2 B 3 Y 2
3 C 2 X 3
4 D 1 W 4  
5 E 0 U 5
COLS

OUTPUT

A B C D E
4 3 2 1 0
Z Y X W U
1 2 3 4 5

Mas com o sinal -U , imprime apenas itens exclusivos, por isso ...

cdup -U <<\COLS
1 A 4 Z 1
2 B 3 Y 2
3 C 2 X 3
4 D 1 W 4  
5 E 0 U 5
COLS

... fica ...

A B C D E
4 3 2 1 0
Z Y X W U

Ou -D apenas para duplicados, com um registro extra por aparência de coluna duplicada. Não é tão ruim ...

cdup -D <<\DATA
1 1 A A 4 Z 1
2 2 B B 3 Y 2
3 3 C C 2 X 3
4 4 D D 1 W 4
5 5 E E 0 U 5
DATA
1 2 3 4 5
1 2 3 4 5
A B C D E 
    
por 14.10.2014 / 22:04
1

Aqui está uma solução gawk que usa coprocessos para alimentar cada coluna para uma instância separada de sha256sum e relata o número total de hashes exclusivos (o número de hashes exclusivos deve coincidir com o número de colunas exclusivas, dado que a colisão hash probabilidade com sha256sum é estatisticamente insignificante). Enquanto alguns podem considerar isso um hackeamento notório, uma vantagem que essa abordagem tem sobre alguns dos outros é que ela não tenta concatenar / transpor os dados e, portanto, é relativamente eficiente em termos de memória.

awk 'BEGIN{for(i=1; i<=6; ++i){s=sprintf("%*s", i+1, ""); a[i]="sha256sum"s}}
    {for (i=1; i<=6; ++i) print $i |& a[i]}
    END{com= "sort | uniq | wc -l"
    for (i=1; i<=6; ++i){close(a[i], "to"); a[i] |& getline x;
    close(a[i]); print x | com};
    close(com)}' file 
    
por 15.10.2014 / 03:13

Tags