今回も参加しました。リモートになってから1年ぐらい経ったような…。
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行を削除する。いろんな方法があるかと思いますが今回はawk
。tail -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させていただきました。ゲームなどにある「実績システム」をシェルに導入したらふとした時にうれしいよねということで実装したという話をしました。
おわり
日常でシェル芸を書いていても達成感とかはまあないのですが、シェル芸勉強会では解けると嬉しいのでいいですよね。解いた後ほかの人の解答を眺めたり、試したりする時間も楽しいですし。
今回も開催ありがとうございました!