たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

第53回シェル芸勉強会に参加しました

今回も参加しました。リモートになってから1年ぐらい経ったような…。

YouTubeアーカイブここから見られます。

Q1

まずは準備として以下のコマンドを実行します

$ seq 1e6 | shuf > a

1~100万までの数値をランダムで並び変えたファイルaを作る。 このファイルの1行目の数値と5行先の数値を足して、2列目に出力してくださいという問題

例えば

1
3
5
7
9
11

だとすると

1 12 <= 1 + 11
3 a  <= 3 + b
5 c  <= 5 + d
...

なるほど。これは先に先頭5行を削除したファイルをpasteすると楽かもですね。やってみます

$ seq 1e6 | shuf > a; paste a <(awk 'NR==6,0' a) | awk 'NF==2{print $1, $1 + $2}'

いいですね。先頭n行を削除する。いろんな方法があるかと思いますが今回はawktail -n +6とかでもいいと思います

Q2

fracファイルは1列目、2列目に数値が書いてあります。これを約分してください。ただし1列目が分子、2列目が分母とします

やくぶん!?と思ったけどfactorで共通部分を消せばいいかなということで

$ cat Sh*/vol.53/frac | factor | awk 'NR==1{for(i=2;i<=NF;i++){b[$i]++}}NR==2{for(i=2;i<=NF;i++)b[$i]--}END{s=1;t=1;for(x in b) if(b[x]>0){s*=x}else if(b[x]<0){t*=x}; print s, t}'
568396410 91

分母、分子のfactorの結果を分母ではそれぞれ数え上げ、分子では引き算する。これにより個数が正なら分母の、個数が負数なら分子の残りだとわかるので、あとはこれをかけ合わせればよいということですね。

ちなみに

$ cat Sh*/vol.53/frac | awk '{print "p "$1"/"$2"r"}' | ruby
(568396410/91)

rubyなら末尾のr有理数を表せるので簡単に計算できる。いいなあこれ。

Q3

numsファイルに記述してある数値を最後の桁で四捨五入する問題。つらい

$ cat She*/vol.53/nums | awk '{print "%."length($0)-4"f\
", $0}'  | xargs -n2 printf

printfで四捨五入ですね。

Q4

以下の手続きをとると、必ずある数値に収束します。それを求めてください

  • 4桁以下のゾロ目でない数aを適当に選び、3桁以下なら頭に0を足して4桁に
  • aの各桁を数字が大きい順にソートしてb、小さい順にソートしてcを作成
  • a=b-cとして2に戻る
$ a=4890; while true; do [[ ${#a} == 3 ]] && a=0$a; b=$(grep -o . <<< $a|sort -nr|tr -d \n); c=$(grep -o . <<< $a|sort -n|tr -d \n);d=$((b-c)); [[ $a == $d ]] && break; a=$d;done; echo $a
6174

6174に収束しました。手続き通りにワンライナーを書いているだけです。桁のソートは

$ grep -o . <<< $a|sort -nr|tr -d \n
$ grep -o . <<< $a|sort -n|tr -d \n

でやっています。横方向のソートが欲しくなりますねえ

Q5

ある範囲のふたつの素数をランダムに選んで、6要素の等差数列を作ってください。という問題。例えばランダムに選んだ二つの素数が11と31だとすると、その差は20なので以下のような等差数列を生成するというもの

11 31 51 71 91 111

awkでえい

$ seq 1e4 | factor | awk 'NF==2{print $2}' | shuf | xargs -n2 | awk 'NF==2{if($1>$2){a=$2;$2=$1;$1=a}print}' | awk '{a=$2;d=$2-$1;for(i=3;i<=6;i++){$i=a+=d};print}' | head

先のawkで大小を入れ替え、後のawkでデータを作ります。$iにどんどん代入していって、フィールドを作っています

Q6

Q5のデータをもとに等差数列すべてが素数な行を出力する問題。なんとなくむずそうに聞こえるのだけど落ち着いて行ごとに処理してみます

$ cat a | awk '{print "echo ",$0, ":  $(openssl prime ",$0}' | sed "s/\$/|awk '{printf NF==4}')/" |bash | awk '$NF=="111111"'

はい…これはちょっと解説しようと思います。まずは最初のawkの出力を見てみます。

$ cat a | awk '{print "echo ",$0, ":  $(openssl prime ",$0}'
echo  1601 3727 5853 7979 10105 12231 :  $(openssl prime  1601 3727 5853 7979 10105 12231
echo  3019 7057 11095 15133 19171 23209 :  $(openssl prime  3019 7057 11095 15133 19171 23209
echo  127 1901 3675 5449 7223 8997 :  $(openssl prime  127 1901 3675 5449 7223 8997
echo  6911 9547 12183 14819 17455 20091 :  $(openssl prime  6911 9547 12183 14819 17455 20091
echo  107 8539 16971 25403 33835 42267 :  $(openssl prime  107 8539 16971 25403 33835 42267
echo  3373 8443 13513 18583 23653 28723 :  $(openssl prime  3373 8443 13513 18583 23653 28723
echo  3307 4159 5011 5863 6715 7567 :  $(openssl prime  3307 4159 5011 5863 6715 7567
echo  809 1997 3185 4373 5561 6749 :  $(openssl prime  809 1997 3185 4373 5561 6749
echo  1597 1747 1897 2047 2197 2347 :  $(openssl prime  1597 1747 1897 2047 2197 2347
echo  4057 9887 15717 21547 27377 33207 :  $(openssl prime  4057 9887 15717 21547 27377 33207

中途半端なワンライナーが出力されます。後段のsedも見てみましょう

$ cat a | awk '{print "echo ",$0, ":  $(openssl prime ",$0}' | sed "s/\
$/|awk '{printf NF==4}')/"
echo  1601 3727 5853 7979 10105 12231 :  $(openssl prime  1601 3727 5853 7979 10105 12231|awk '{printf NF==4}')
echo  3019 7057 11095 15133 19171 23209 :  $(openssl prime  3019 7057 11095 15133 19171 23209|awk '{printf NF==4}')
echo  127 1901 3675 5449 7223 8997 :  $(openssl prime  127 1901 3675 5449 7223 8997|awk '{printf NF==4}')
echo  6911 9547 12183 14819 17455 20091 :  $(openssl prime  6911 9547 12183 14819 17455 20091|awk '{printf NF==4}')
echo  107 8539 16971 25403 33835 42267 :  $(openssl prime  107 8539 16971 25403 33835 42267|awk '{printf NF==4}')
echo  3373 8443 13513 18583 23653 28723 :  $(openssl prime  3373 8443 13513 18583 23653 28723|awk '{printf NF==4}')
echo  3307 4159 5011 5863 6715 7567 :  $(openssl prime  3307 4159 5011 5863 6715 7567|awk '{printf NF==4}')
echo  809 1997 3185 4373 5561 6749 :  $(openssl prime  809 1997 3185 4373 5561 6749|awk '{printf NF==4}')
echo  1597 1747 1897 2047 2197 2347 :  $(openssl prime  1597 1747 1897 2047 2197 2347|awk '{printf NF==4}')
echo  4057 9887 15717 21547 27377 33207 :  $(openssl prime  4057 9887 15717 21547 27377 33207|awk '{printf NF==4}')

openssl primeを使うと、その数値が素数かどうかを判定して返してくれます。その出力を見てみましょう

$ openssl prime 1601 3727 5853 7979 10105 12231
641 (1601) is prime
E8F (3727) is prime
16DD (5853) is not prime
1F2B (7979) is not prime
2779 (10105) is not prime
2FC7 (12231) is not prime

注目したいのは列の数です。素数じゃないときは not が存在するので、列の数が5になります。反対に素数の時は4です。

なのでこれを以下のようなawkに渡すと

awk '{printf NF==4}'

素数かどうかを0,1で表すことができます。このとき改行なしで出力されるので、6個のデータは1列にまとまります。というわけですべて素数の場合は 111111 という文字列が得られるはずです

あとはこれをgrepなりすれば良いですね。ただしデータの方に 111111 が含まれる可能性があるので、ここでもawkを使って処理しています

とまぁここまで頑張って書きましたが、これteipで以下のように書けます

$ cat a | teip -f 1-6 factor | awk 'NF==12' | sed 's/[0-9]+: //g'

素晴らしい・・・。横方向への部分適用はteipが便利ですね。出力が横に並ぶことを知らなかったので次からは使えるといいですね

Q7

力尽きました。

LT

今回はLTさせていただきました。ゲームなどにある「実績システム」をシェルに導入したらふとした時にうれしいよねということで実装したという話をしました。

www.slideshare.net

おわり

日常でシェル芸を書いていても達成感とかはまあないのですが、シェル芸勉強会では解けると嬉しいのでいいですよね。解いた後ほかの人の解答を眺めたり、試したりする時間も楽しいですし。

今回も開催ありがとうございました!