【自習】第64回シェル芸勉強会【RやPARI/GPも便利】

3月の最終週から4月末日まで切り文字ステッカー×1万枚の製造に休日返上で取り組んでいたため、当日のオンライン参加は見送ることになり、ゴールデンウィークに入ってからようやく問題を解くことができました。ちなみに今回は統計にまつわる問題が多かったのでR(Rscript)やPARI/GP(gp)がかなり役に立ちました。



Q1 (@grethlenさんの出題 改)

次のファイルreply.txtは、電子メールの送信日時、送受信者、件名が記録されたデータです。

$ cat reply.txt
2023-02-15 11:23:43 太郎->花子 こんにちは
2023-02-15 21:23:43 太郎->花子 返信無用
2023-02-16 12:35:30 花子->太郎 Re:こんにちは
・・・

返信のメールのタイトルが、”Re:”+”もとのメールのタイトル”だと仮定して、「もとのメールの件名」「行きのメールの送受信者」、「帰りのメールの送受信者」、「メールが位置往復した時間」に整形し、時間の短いほうから出力してください。返信のないメールは出力しなくて大丈夫です。

一行だけ、例を示します。

こんにちは 太郎->花子 花子->太郎 25.1964

A1

$ awk '{if($NF~/^Re:/){sub(/:/,"_",$NF);$NF=$NF "$"}else{$NF="_" $NF};print}' reply.txt | sort -t_ -k2,2 | sed -Ez 's/( _([^\n]+))\n([^\n]+) Re_\2\$/\1\t\3/g' | awk '/\t/{sub(/^_/,"",$4);printf "%s %s %s ",$4,$3,$7;system("dateutils.ddiff "$1"T"$2" "$5"T"$6" -f %S")}' | sort -k4,4n | awk '{print $1,$2,$3,$4/3600}'
やったー 太郎->花子 花子->太郎 3.18278
首記の件 花子->太郎 太郎->花子 22.8919
こんにちは 太郎->花子 花子->太郎 25.1964
げんき? 太郎->花子 花子->太郎 52.0769
毎々お世話になります 太郎->花子 花子->太郎 81.9994

Q2

平均値ゼロ、標準偏差1の正規分布にしたがう乱数を延々と出力してください。よくわからない人は、0〜1の乱数を12個足して6を引いた値を出力してください。

A2

# Rの`rnorm()`を利用
$ Rscript -e 'repeat{r<-rnorm(1);cat(r,"\n")}'
0.7553825
-0.129103
0.09973892
-0.01873832
-0.06047213
-0.0645322
1.596996
0.09596169
-0.1655089
-0.4798252
(...略...)

Q3

次のファイルmatを5x5行列だと考えて、対称行列であることを示してください。 (mat_asynという非対称行列もあるので、検証にお使いください。)

$ cat mat
1 2 3 4 8
2 1 9 0 7
3 9 1 5 5
4 0 5 1 0
8 7 5 0 9

A3

$ awk '{for(n=1;n<=NF;n++)a[NR][n]=$n}END{for(m=1;m<=NF;m++){for(n=1;n<=NF;n++){if(n==m)continue;if(a[m][n]!=a[n][m]){print FILENAME"は非対称行列です";exit}}}print FILENAME"は対称行列です"}' mat
matは対称行列です

なお、非対称行列mat_asynでの出力は次の通りです。

$ awk '{for(n=1;n<=NF;n++)a[NR][n]=$n}END{for(m=1;m<=NF;m++){for(n=1;n<=NF;n++){if(n==m)continue;if(a[m][n]!=a[n][m]){print FILENAME"は非対称行列です";exit}}}print FILENAME"は対称行列です"}' mat_asyn
mat_asynは非対称行列です

Q4

次のファイルnums.0は、1列目になにかの数値、2列目にキーがついたデータファイルです。

$ cat nums.0
1 0
2 1
3 2
5 0
6 1
9 2
10 0
11 1

Q4小問1

各キーの数値の平均値を求め、次のようなファイルmean.1をつくってください。

0 5.33333
1 6.33333
2 6

Q4小問2

nums.0について、キーを入れ替えます。各行のnums.0の数値について、mean.1の2列目から最も近い数字を見つけ、その数字のキーをnums.0の2列目に付加してnums.1というファイルに出力してください。

小問1、2を繰り返してmean.2nums.2、・・・と作っていくと、k近傍法のクラスタリングになるので、余力のある人はやってみてください。

A4

A4小問1

$ awk '{s[$2]=s[$2]+$1;c[$2]++}END{for(k in s)print k,s[k]/c[k]}' nums.0 | sort -n | tee mean.1
0 5.33333
1 6.33333
2 6

A4小問2

$ awk 'FILENAME=="mean.1"{m[$1]=$2}FILENAME=="nums.0"{for(k in m){a=sqrt((m[k]-$1)^2);d[k]=a;n[a]=k}asort(d,c);print $1,n[c[1]]}' mean.1 nums.0
1 0
2 0
3 0
5 0
6 2
9 1
10 1
11 1

Q5

自然数 $n$ に対し、オイラーのファイ関数: \(\phi(n) = n \displaystyle\prod_{i=1}^k ( 1 - 1/p_i)\) の値を返すワンライナーを実装してください。 $p_i$ は、 $n$ の素因数です。

echo n | ...

A5

$ echo 12 | while read n; do for m in $(seq $n); do [ $(awk -v m=$m -v n=$n "BEGIN{while(n!=0){t=m;m=n;n=t%n}print m}") -eq 1 ] && printf "$m "; done; echo; done | sed 's/ $//' | awk '{print NF}'
4

gpeulerphi()を使った別解は次の通りです。

$ echo 12 | sed 's/.*/eulerphi(&)/' | gp -fq
4

Q6

Q6小問1

seq 100 |から始めて、各行の数字に対し、互いに素な自然数を列挙してください。最小公倍数を求めるときにはRubyの数字.lcm()やPythonのmath.lcm()などが便利です。

Q6小問2

列挙した自然数の数が、ファイ関数の値と一致することを確認してください。ワンライナーである必要はありません。

A6

A6小問1

$ seq 100 | while read n; do printf "$n ["; for m in $(seq $n); do [ $(awk -v m=$m -v n=$n "BEGIN{while(n!=0){t=m;m=n;n=t%n}print m}") -eq 1 ] && printf "$m, "; done; echo; done | sed 's/, $/]/'
1 [1]
2 [1]
3 [1, 2]
4 [1, 3]
5 [1, 2, 3, 4]
6 [1, 5]
7 [1, 2, 3, 4, 5, 6]
8 [1, 3, 5, 7]
9 [1, 2, 4, 5, 7, 8]
10 [1, 3, 7, 9]
11 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
12 [1, 5, 7, 11]
(...略...)
98 [1, 3, 5, 9, 11, 13, 15, 17, 19, 23, 25, 27, 29, 31, 33, 37, 39, 41, 43, 45, 47, 51, 53, 55, 57, 59, 61, 65, 67, 69, 71, 73, 75, 79, 81, 83, 85, 87, 89, 93, 95, 97]
99 [1, 2, 4, 5, 7, 8, 10, 13, 14, 16, 17, 19, 20, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 46, 47, 49, 50, 52, 53, 56, 58, 59, 61, 62, 64, 65, 67, 68, 70, 71, 73, 74, 76, 79, 80, 82, 83, 85, 86, 89, 91, 92, 94, 95, 97, 98]
100 [1, 3, 7, 9, 11, 13, 17, 19, 21, 23, 27, 29, 31, 33, 37, 39, 41, 43, 47, 49, 51, 53, 57, 59, 61, 63, 67, 69, 71, 73, 77, 79, 81, 83, 87, 89, 91, 93, 97, 99]

A6小問2

# A6小問1のワンライナーで得られたフィールド数から1を引いたものと
# `gp``eulerphi()`で得られた値を`diff`する
$ diff -su <(seq 100 | while read n; do printf "$n ["; for m in $(seq $n); do [ $(awk -v m=$m -v n=$n "BEGIN{while(n!=0){t=m;m=n;n=t%n}print m}") -eq 1 ] && printf "$m, "; done; echo; done | sed 's/, $/]/' | awk '{print NF-1}') <(seq 100 | while read n; do echo "eulerphi($n)" | gp -fq; done)
ファイル /dev/fd/63 と /dev/fd/62 は同一です
Written on May 5, 2023