AWKなしのマルチパス方式

AWKなしのマルチパス方式

今日、私は他のウェブサイトでシェルスクリプトに関する興味深い質問を見つけました。問題は、他の行列の値に基づいて行列を構築することです。

https://askubuntu.com/questions/884372/count-number-in-two-columns-and-generate-matrix

元の行列は次のとおりです。

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

出力行列は次のようにする必要があります

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

ご覧のとおり、最初の行列が2番目の行列の要約である場合

私は次のように問題を解決しようとします。

最初の行列をtest01.txtというファイルに保存しました。区切り文字としてスペースを使用します。

最初の列から一意の要素を抽出して行に変換し、newというファイルに保存します。

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

2番目の列の一意の要素を抽出し、new2というファイルに保存します。

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

newの最初の要素として0を追加し、new1として保存します。

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

ファイルの最初の列には5つの異なる要素があるため、クロス比較のためにnew2ファイルの各行に5つのゼロが追加されます。

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

そして、new3の内容をnew1ファイルの最後に追加します。

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

これで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

それから私は詰まった。

new1の行列を見て、必要に応じて0を変更できるように、各要素をtest01の行と比較する方法がわかりません。最終結果は次のようになります。

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

たぶん、それほど多くの中間ファイルなしでこれまで使ったことよりも結果を得るより効率的な方法があるかもしれません。

文が長くなってすみません。

ベストアンサー1

AWKなしのマルチパス方式

まず、ファイルを読み取り、行と列のラベルを抽出します。その後、ゼロと最初の行の各列ラベルが印刷されます。

このループはラベルなしの行処理を担当します。まず、行ラベルを印刷してから、ファイル内の対応する(行、列)ペアに一致するすべての項目を検索します。dc複数の行が返されると、この結果の3番目の列にある項目が追加されます。

このアプローチの明らかな問題は、ファイルを読むことです。アイテムごとに1回結果マトリックスから。したがって、行ラベルと列ラベルを取得した最初の2回を計算すると、例は22回読み取られます。

呼び出し方法は次のとおりです./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

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"
    }
}

開始ブロックは、いくつかの変数を設定し、配列巡回をソートするようにGNU awkを設定します。次の2つのブロックは、入力をスキャンしながら必要に応じて名前と部門を追加します。 3番目のブロックは各累計を計算します。

「人が読める」形式が必要ない場合は、この行…_length = max(…をコメントアウトしてください。

このENDブロックは、以前に作成された配列を繰り返し、すべての出力とフォーマットが行われる場所です。これにより、出力テーブルの各エントリに対して1つの転送ではなく、入力ファイルに対して1つの転送が許可されます。

おすすめ記事