「第53回シェル芸勉強会」リモート参加レポート
2021-04-24(土)「第53回シェル芸勉強会」にオンライン参加し、久しぶりに参加レポートを書きました。コロナ禍の影響で、シェル芸勉強会の福岡サテライト会場は長いこと開催しておりません。前回は「第45回シェル芸勉強会」2019年12月末だったようです。問題作成と解説の上田さん、ありがとうございます。参加者の皆様もお疲れ様でした。
シェル芸勉強会はここ1年くらいオンライン参加の形態になっています。問題出題者の上田さんが自宅からYoutube配信されるのを見聞きしながら、Twitterで問題を見たり解答したりするようなやり方です。最近では雑談やツッコミなどの目的でDiscodeも併用もしています。二次会もオンライン飲み会になっています。
勉強会の情報
勉強会主催者の上田さんが公開されているリンク集をご覧ください。
勉強会の内容について
主催者の上田さんがYoutube配信で出題と解説をされている環境はこちらです。
twitter.comセッティング完了 #シェル芸 pic.twitter.com/ihyNpQX4hO
— 上田隆一 (@ryuichiueda) 2021年4月24日
今回の問題は、数学的な問題をAWKでゴリ押しする系のようです。
問題と解答は、出題者上田さんが公開されているページをご覧ください。
問題と解答
【問題と解答】jus共催 第53回シェル芸が好きです。でもゾウさんのほうがもっと好きですシェル芸勉強会 | 上田ブログ
私なりに考えた解答例を以下に掲載いたします。
Q1
1から100万までがランダムに並んだデータで、5列先の数を足して2列目に出力する問題。
$ seq 1000000 | shuf > a $ head a 742505 19482 511202 49851 406516 617563 227367 821010 753384 906981
paste
コマンドを使った解答がこちら。tail
コマンドで6行目以降のデータを作って、横に並べて足し算します。
$ paste a <(tail -n +6 a) 742505 617563 19482 227367 511202 821010 49851 753384 406516 906981 617563 115432 227367 45000 821010 922566 753384 11506 906981 10750 (以下省略)
あとはデータが2列並んだ行のみ、横に並んだ2つを足し算した結果を2列目に表示して完成です。
$ paste a <(tail -n +6 a) | awk 'NF==2{print $1,$1+$2}' > b $ head b 742505 1360068 <- 742505 + 617563 19482 246849 <- 19482 + 227367 511202 1332212 49851 803235 406516 1313497 617563 732995 227367 272367 821010 1743576 753384 764890 906981 917731 (以下省略)
twitter.com#シェル芸 Q1間違えてた。tail -n +6だった。
— ぱぴろんちゃん😱🙀 (@papiron) 2021年4月24日
seq 1000000 | shuf > a; paste a <(tail -n +6 a) | awk 'NF==2{print $1,$1+$2}'
AWK単体での別解はこちらです。各行のデータを配列に保存し、6行目以降から現在行のデータと5つ前のデータを足し算します。
$ awk '{a[NR]=$0}NR>=6{print a[NR-5],$0+a[NR-5]}' a > b
Q2
数値計算ではなく、有理数として約分する問題です。最初は素因数分解したのを縦に並べてどうにか出来ないか?と考えたがダメでした。
$ cat frac | factor | rs -T | sed 1d 2 3 3 3 3 7 3 13 5 633543227197 347 54601 633543227197
出題者上田さんの解答例では、各素因数の数を計算し、分子の数から分母の数を引き算していました。難しい。
別解として、Rubyの有理数を扱う機能を使う例が出ていました。RubyのPEPL環境irb
で試してみます。
$ irb irb(main):001:0> 3240933263267302464930/518871903074343r 【最後にrを付ける】 => (568396410/91) 【約分された結果が表示される】 irb(main):002:0> Rational(3240933263267302464930,518871903074343) => (568396410/91)
解答例では末尾にr
を記載したやり方が出ていましたが、Rationalクラスを明示的に書いた解答を作ってみました。
$ cat frac | awk '{printf "puts Rational(%s, %s)\n",$1,$2}' | ruby | tr / ' ' 568396410 91
twitter.com#シェル芸 Q2の結論、有理数が扱えるRuby便利
— ぱぴろんちゃん😱🙀 (@papiron) 2021年4月24日
RubyのRationalクラスのマニュアルはこちらです。 docs.ruby-lang.org
Q3
ややこしい四捨五入をする問題。Q2でRubyが便利だったので、こちらもRubyで楽をする解答を作ってみました。
まずAWKでRubyのコードを作成します。
$ cat nums | awk '{print "puts "$0".round("length($2)-1")"}' FS=. puts -0.327.round(2) puts 2.33333.round(4) puts 4.0000000000999995.round(15)
次にrubyコマンドでコードを実行して完成です。
$ cat nums | awk '{print "puts "$0".round("length($2)-1")"}' FS=. | ruby -0.33 2.3333 4.0000000001
Q4
連続して処理を続けて、数が収束するのを確認する問題。一旦処理を行う関数を作成し、関数をパイプで数珠つなぎ実行するというやり方で考えました。 出力を標準エラー出力に出せば、パイプで連続接続しても確認出来るというアイデアを使わせていただきました。
$ f(){ awk '{print > "/dev/stderr"; printf "%04d\n",$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc; }; eval $(yes f | head | xargs | tr ' ' '|' | sed 's/^/echo 9467|/') |& xargs 9467 5085 7992 7173 6354 3087 8352 6174 6174 6174 6174
出題者上田さんが作成された、コマンドをパイプで数珠つなぎして実行するjuz
というコマンドがあるのを思い出し、使ってみようとしたがうまく動きません。数珠つなぎするのが外部コマンドはなく関数なのが原因だと思われます。
$ f(){ awk '{print > "/dev/stderr"; printf "%04d\n",$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc; }; echo 9467 | juz 10 f
twitter.com#シェル芸 Q4解答これで良い?完全なワンライナーじゃなくて関数作ったけど。
— ぱぴろんちゃん😱🙀 (@papiron) 2021年4月24日
f(){ awk '{printf "%04d\n",$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc; }; echo 9467 | juz 10 f
関数ではなくシェルスクリプトとして保存(つまり外部コマンドにした)して実行すれば動きました。
$ echo "awk '{print > \"/dev/stderr\"; printf \"%04d\n\",\$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc" > f; echo 497 | juz 10 sh f |& xargs 497 9261 8352 6174 6174 6174 6174 6174 6174 6174 6174
上田さん作のjuz
コマンドなどのglueutilsはこちらです。juz
コマンドは残念ながらmacOSではコンパイル出来ませんでした。(ヘッダーファイルprctl.h
がmacOSには付属していないため)
github.com
Q5
小問1
2つの素数を並べて、その差で等差数列を作ります。とりあえずはこんな感じでしょうか。
$ seq 1000 | factor | awk 'NF==2{print $2}' | shuf | head -n 2 | sort -n | xargs | awk '{printf "%d %d ",$1,$2;a=$2;d=$2-$1;for(i=1;i<=4;i++){a=a+d;printf a" "};print ""}' 7 823 1639 2455 3271 4087
小問2
こちらは上記のワンライナーを無限ループさせれば良いです。
$ while true; do seq 1000 | factor | awk 'NF==2{print $2}' | shuf | head -n 2 | sort -n | xargs | awk '{printf "%d %d ",$1,$2;a=$2;d=$2-$1;for(i=1;i<=4;i++){a=a+d;printf a" "};print ""}'; done 557 593 629 665 701 737 631 733 835 937 1039 1141 2 19 36 53 70 87 41 593 1145 1697 2249 2801 167 193 219 245 271 297 (以下省略)
Q6
出力結果の各列を素因数分解し、素数の数が6つになる行を探せば良いです。具体的には、Q5小問1のワンライナーで出力される結果を各列素因数分解し、素数の数が6個になる場合を探します。
$ Q5小問1のワンライナー | factor | awk '{$1="";print}' | tr '\n' ' ' 41 127 3 71 13 23 5 7 11 3 157 【この出力が6列ならば求める結果になる】
while
で無限ループを作った解答がこちらです。
$ while true; do seq 1000 | factor | awk 'NF==2{print $2}' | shuf | head -n 2 | sort -n | xargs | awk '{printf "%d %d ",$1,$2;a=$2;d=$2-$1;for(i=1;i<=4;i++){a=a+d;printf a" "};print ""}' | factor | awk '{$1="";print}' | tr '\n' ' ' | awk 'NF==6'; done 7 157 307 457 607 757 229 619 1009 1399 1789 2179 (以下省略)
出題者上田さんの解答例でteip
コマンドを使ったものがありました。teip
コマンドは「ぐれさん」の名前でコミュニティ活動されている方が開発されました。
github.com
teip
コマンドは、特定の列に対してフィルタコマンドの処理を適用出来るのがポイントです。先日の業務でもこのコマンドが欲しい事例がありました。例えば2列目のデータのみにsed
コマンドの処理を適用する例はこちらです。
$ echo a b c | teip -f 2 sed 's/.*/(&)/' a (b) c
juz
コマンドと合わせ技だとこんなことが出来ます。
$ echo a b c | juz 10 teip -f 2 sed 's/.*/(&)/' a ((((((((((b)))))))))) c
この問題でのteip
コマンドの使い道は次の通りです。横に並んだ数列に対してfactor
コマンドを適用すると縦に並んでしまいますが、teip
コマンドを併用すると横に並べたまま出力します。
$ seq 2 5 | xargs | factor 2: 2 3: 3 4: 2 2 5: 5 $ seq 2 5 | xargs | teip -f 1-4 factor 2: 2 3: 3 4: 2 2 5: 5
teip
コマンドを使うと、素数の数を数える部分を下記のように書くことが出来ます。1列目と2列目は素数なのが分かっているため、3列目以降のみ適用しています。
$ Q5小問1のワンライナー | teip -f 3-6 factor 383 571 759: 3 11 23 947: 947 1135: 5 227 1323: 3 3 3 7 7 【この出力が10列なら求める結果になる】
Q7
これは勉強会の時間内ではギブアップでした。後日トライした結果がこちらです。
twitter.com#シェル芸 Q7のコッホ曲線やっとこさ出来た、、、距離やラジアンを使って地道に座標の計算をawkで頑張る問題という事がやっとこさ理解出来た。 pic.twitter.com/udd0klKIMM
— ぱぴろんちゃん😱🙀 (@papiron) 2021年5月2日
2つの座標について、距離と角度を使って追加する中間点を3つ計算し、座標データを追加していく処理をおこないます。 2つの座標を(x1,y1)、(x5,y5)とし、3つの中間点を(x2,y2)(x3,y3)(x4,y4)とします。 (x2,y2)と(x4,y4)は、(x1,y1)と(x5,y5)間を3等分した位置にあります。
下記計算処理の手順を、2から6まで毎行毎に繰り返していけば良いです。
- 1列目のデータの場合は、データをx1とy1に入力。
- 現在の行のデータを(x5,y5)に代入
- (x2,y2)を計算。(x1,y1)と(x5,y5)間で1:2の場所にある。
- (x3,y3)を計算。(x2,y2)を基準にする。(x1,y1,)(x5,y5)間の距離の3分の1を半径とし、60度(π/3ラジアン)回転した位置にある。
- (x4,y4)を計算。(x1,y1)と(x5,y5)間で2:1の場所にある。
- (x5,y5)の値を(x1,y1)へ代入して次の行へ移動。手順2から繰り返す。
上記の処理をAWKの1行プログラミングで書いて関数として定義しました。関数をたくさん通すほどコッホ曲線の深度が深くなっていきます。。
【関数定義】 $ f(){ awk 'NR==1{x1=$1;y1=$2}NR!=1{print x1,y1;x5=$1;y5=$2;a=atan2(y5-y1,x5-x1);r=sqrt((x5-x1)^2+(y5-y1)^2)/3;x2=x1+r*cos(a);y2=y1+r*sin(a);print x2,y2;x3=x2+r*cos(a+3.141592/3);y3=y2+r*sin(a+3.141592/3);print x3,y3;print x1+2*r*cos(a),y4=y1+2*r*sin(a);x1=x5;y1=y5}END{print x1,y1}'; } $ cat triangle | f | gnuplot -e 'set terminal png;set size ratio -1;set output "./hoge.png";plot "-" w l' 【画像が作成される】 $ cat triangle | f | f | f | gnuplot -e 'set terminal png;set size ratio -1;set output "./hoge.png";plot "-" w l' 【深度がより深い画像が作成される】
TLと二次会
LightningTalkは2つ発表がありました。シェル(シェルスクリプトでは無い!)を書いていて付随するコマンド作った話題と、コマンドを使う勉強のモチベーションをアップ!(某〇〇ではない!)する話題でした。二次会は初めてのgatherを体験しました。ゲーム感覚で面白いですね。
終わりに
久しぶりにシェル芸勉強会の参加記事を書きました。色々書こうとすると結構時間がかかってしまいますが、アウトプットしておくと気分的にも良いですね。 コロナ禍の影響はいつまで続くのか分からないけど、リモート参加の経験値を上げておく機会だと思うことにしましょうか。