日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げまする。

「第53回シェル芸勉強会」リモート参加レポート

2021-04-24(土)「第53回シェル芸勉強会」が開催されたので、久しぶりに参加レポートを書いた。コロナ禍の影響で、シェル芸勉強会の福岡サテライト会場は長いこと開催していない。前回は「第45回シェル芸勉強会」2019年12月末だったようだ。勉強会参加の記事も長らく書いて無かった。久しぶりの投稿である。

シェル芸勉強会はここ1年くらいオンライン参加の形態になっている。上田会長が自宅からYoutube配信されるのを見聞きしながら、Twitterで問題を見たり解答したりするようなやり方。最近では雑談やツッコミなどの目的でDiscodeも併用もしている。二次会もオンライン飲み会になっている

勉強会募集サイト

勉強会開催アナウンス

usptomo.doorkeeper.jp

勉強会開催報告など

シェル芸勉強会リンク集

jus共催 第53回シェル芸勉強会リンク集 | 上田隆一の仕事とか

Youtubeライブ配信の録画

www.youtube.com

Togetterまとめ

togetter.com

参加報告記事など

xztaityozx.hatenablog.com

勉強会の内容について

毎度のことながら、多忙の中問題作成と配信を行われている上田会長には頭が下がる。 ちなみにYoutube配信する環境は、パソコン3台使ってこんな感じのようだ。

twitter.com

今回の問題は、数学的な問題を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

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

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

関数ではなくシェルスクリプトとして保存して実行すれば動いた。

$ 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

2つの座標について、距離と角度を使って追加する中間点を3つ計算し、座標データを追加していく処理をおこなう。 2つの座標を(x1,y1)、(x5,y5)とし、3つの中間点を(x2,y2)(x3,y3)(x4,y4)とする。 (x2,y2)と(x4,y4)は、(x1,y1)と(x5,y5)間を3等分した位置にある。

f:id:papiro:20210503182549p:plain
座標の位置説明図

下記計算処理の手順を、2から6まで毎行毎に繰り返していけば良い。

  1. 1列目のデータの場合は、データをx1とy1に入力。
  2. 現在の行のデータを(x5,y5)に代入
  3. (x2,y2)を計算。(x1,y1)と(x5,y5)間で1:2の場所にある。
  4. (x3,y3)を計算。(x2,y2)を基準にする。(x1,y1,)(x5,y5)間の距離の3分の1を半径とし、60度(π/3ラジアン)回転した位置にある。
  5. (x4,y4)を計算。(x1,y1)と(x5,y5)間で2:1の場所にある。
  6. (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つ発表があった。シェルを書いていて(シェルスクリプトでは無い!)付随するコマンド作った話題と、コマンドを使う勉強のモチベーションをアップ!(某〇〇ではない!)する話題。 www.youtube.com

www.slideshare.net

二次会は初めてのgatherを体験。ゲーム感覚で面白い。

終わりに

久しぶりにシェル芸勉強会の参加記事を書いた。色々書こうとすると結構時間がかかってしまうが、アウトプットしておくと気分的にも良い。 コロナ禍の影響はいつまで続くのか分からないけど、リモート参加の経験値を上げておく機会だと思うことにしよう。 最後になったが、問題作成と配信をしていただく上田会長、毎回本当にお世話になります。参加者の方々もお疲れ様でした。