Construa uma matriz baseada nos valores de outra matriz

1

Hoje eu encontrei uma questão interessante sobre scripts de shell em outro site. A questão foi construir uma matriz baseada nos valores de outra matriz

link

A matriz original é assim:

joe it 9
wolf it 10
wolf pr 9
mark pm 6
jack pr 20
anton pm 5
joe pm 20
mark sa 35

a matriz de saída deve ser assim

0 anton jack joe mark wolf
it 0 0 9 0 10
pm 5 0 20 6 0
pr 0 20 0 0 9
sa 0 0 0 35 0

Como você vê, a segunda matriz é um resumo se a primeira

Eu tentei resolver o problema da seguinte forma:

Salvei a primeira matriz no arquivo chamada test01. Eu usei um espaço em branco como separador

Extraia os elementos distintos da primeira coluna, converta-os em uma linha e salve-os em um arquivo chamado new

cut -d ' ' -f1-1 test01 |sort |uniq |awk  '{ printf( "%s ", $1 ); } END { printf( "\n" ); }' > new

Extraia os elementos distintos da segunda coluna e salve em um arquivo chamado new2

cut -d ' ' -f2-2 test01|sort|uniq > new2

Adicione um 0 como primeiro elemento do novo e salve-o como novo1

while read line; do echo "0 $line"; done < new > new1

Como há 5 elementos diferentes no arquivo na primeira coluna, 5 zeros foram adicionados a cada linha do arquivo new2 para fazer as comparações cruzadas

while read line; do echo "$line 0 0 0 0 0"; done < new2 > new3

E o conteúdo de new3 foi adicionado ao final do arquivo new1

while read line; do echo $line |awk '{print $1,$2}'; done < new1

Agora, o conteúdo de new1 é:

0 anton jack joe mark wolf
it 0 0 0 0 0
pm 0 0 0 0 0
pr 0 0 0 0 0
sa 0 0 0 0 0

foi quando eu fiquei preso.

Eu não sei como navegar na matriz em new1 e comparar cada elemento com as linhas de test01 para substituir 0 quando for necessário. O resultado final deve ser:

0 anton jack joe mark wolf
it 0 0 9 0 10
pm 5 0 20 6 0
pr 0 20 0 0 9
sa 0 0 0 35 0

Talvez exista um método mais eficiente do que o usado até agora para obter o resultado sem muitos arquivos intermediários

Peço desculpas pela duração da postagem

    
por Emilio Galarraga 18.02.2017 / 02:37

1 resposta

1

Um método multi-pass sem AWK

Isso começa lendo o arquivo para extrair os rótulos de linha e coluna. Em seguida, ele imprime um 0 seguido por cada rótulo de coluna para a primeira linha.

O loop cuida das linhas sem rótulo. Primeiro, imprime o rótulo da linha e, em seguida, pesquisa todas as entradas no arquivo que correspondem a esse par (linha, coluna). As entradas na terceira coluna desse resultado são somadas com dc , no caso de várias linhas serem retornadas.

Um problema evidente com essa abordagem é que o arquivo é lido uma vez por entrada na matriz de resultados. Então, contando os dois passes iniciais para obter os rótulos de linha e coluna, seu exemplo seria lido 22 vezes!

Ligue como ./contingency-table input-file :

#!/bin/sh
# file: contingency-table

columns=$(cut -d' ' -f 1 "$1" | sort | uniq)
rows=$(cut -d' ' -f 2 "$1" | sort | uniq)

printf '0'
printf ' %s' ${columns}
printf '\n'

for row in ${rows}; do
  printf "${row} "
  for col in ${columns}; do
    (grep "${col} ${row}" "${1}" \
     | cut -d' ' -f 3            \
     | tr '\n' '+'
     printf '\n')                \
    | sed -e 's/^/0 /'           \
          -e 's/$/pq/'           \
    | dc                         \
    | tr '\n' ' '
  done
  printf '\n'
done

Um método mais eficiente usando o AWK

#!/usr/bin/awk -f

function max(val1, val2) {
    return ((val1 > val2) ? val1 : val2)
}

BEGIN {
    name_length = 0
    department_length = 0
    # This line influences sorting in GNU awk
    PROCINFO["sorted_in"] = "@ind_str_asc"
}

(!($1 in names)) {
    names[$1]
    name_length = max(length($1), name_length)
}

(!($2 in departments)) {
    departments[$2]
    department_length = max(length($2), department_length)
}

{
    hours[$2, $1] += $3
}

END {
    printf "%" department_length "s", 0
    for (name in names) {
        printf " %" name_length "s", name
    }
    printf "\n"
    for (department in departments) {
        printf "%" department_length "s", department
        for (name in names) {
            printf " %" name_length "d", hours[department, name]
        }
        printf "\n"
    }
}

O bloco inicial configura algumas variáveis e configura o GNU awk para ordenar travessias de matriz. Os próximos dois blocos adicionam nomes e departamentos conforme necessário, enquanto examinam a entrada. O terceiro bloco calcula cada total em execução.

Se você não quiser a formatação "humana legível", comente as …_length = max(… linhas.

O bloco END é onde toda a saída e formatação acontece, percorrendo as matrizes criadas anteriormente. Isso permite uma única passagem sobre o arquivo de entrada, em vez de um por entrada na tabela de saída.

    
por 19.02.2017 / 20:47