无需 AWK 的多遍方法

无需 AWK 的多遍方法

今天我在其他网站上发现了一个关于shell脚本的有趣问题。问题是根据另一个矩阵的值构建一个矩阵

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

如您所见,如果第一个矩阵是第二个矩阵的摘要

我尝试按如下方式解决该问题:

我将第一个矩阵保存在名为 test01.txt 的文件中。我使用空格作为分隔符

从第一列中提取不同的元素,将其转换为一行并将其保存到名为 new 的文件中

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

提取第二列的不同元素并保存在名为 new2 的文件中

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

添加 0 作为 new 的第一个元素并将其保存为 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 中的矩阵并将每个元素与 test01 的行进行比较,以便在必要时替换 0。最终结果必须是:

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 的多遍方法

首先读取文件以提取行标签和列标签。然后它打印 0,后跟第一行的每个列标签。

该循环负责处理非标签行。首先,它打印行标签,然后搜索文件中与该(行、列)对匹配的所有条目。dc如果返回多行,则此结果的第三列中的条目将与 相加。

这种方法的一个明显问题是文件被读取每个条目一次在结果矩阵中。因此,计算最初两次获取行标签和列标签的次数,您的示例将被读取 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"
    }
}

begin 块设置一些变量并配置 GNU awk 对数组遍历进行排序。接下来的两个块在扫描输入时根据需要添加名称和部门。第三块计算每个运行总计。

如果您不想要“人类可读”的格式,请注释掉这些…_length = max(…行。

END块是通过遍历之前创建的数组来进行所有输出和格式化的地方。这允许对输入文件进行一次传递,而不是对输出表中的每个条目传递一次。

相关内容