오늘 저는 다른 웹사이트에서 쉘 스크립트에 관한 흥미로운 질문을 발견했습니다. 문제는 다른 행렬의 값을 기반으로 행렬을 구성하는 것입니다.
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
new의 첫 번째 요소로 0을 추가하고 new1로 저장합니다.
while read line; do echo "0 $line"; done < new > new1
파일의 첫 번째 열에는 5개의 서로 다른 요소가 있으므로 교차 비교를 위해 new2 파일의 각 행에 5개의 0이 추가됩니다.
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가 없는 멀티패스 방식
먼저 파일을 읽어 행 및 열 레이블을 추출합니다. 그런 다음 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"
}
}
시작 블록은 일부 변수를 설정하고 배열 순회를 정렬하도록 GNU awk를 구성합니다. 다음 두 블록은 입력을 스캔하는 동안 필요에 따라 이름과 부서를 추가합니다. 세 번째 블록은 각 누계를 계산합니다.
"사람이 읽을 수 있는" 형식을 원하지 않으면 이 …_length = max(…
줄을 주석 처리하세요.
이 END
블록은 이전에 생성된 배열을 반복하여 모든 출력 및 형식화가 발생하는 곳입니다. 이를 통해 출력 테이블의 각 항목에 대해 하나의 전달이 아닌 입력 파일에 대해 하나의 전달이 허용됩니다.