先日開催された、第38回シェル芸勉強会に参加してきたので、その復習。 実は1個飛ばしてたのでちょっと久しぶりな感じ。今回は数学の問題が多いらしい。

問題及び模範解答はこちら。最初に、問題等に使用するファイルをgitからcloneしておくといい。

Q1.

今回の勉強会のタイトル、Unicodeの文字を利用してちょっと読みにくい状態にしてある。

echo 'jus共催 第38回҈҈҉҈҈҉シ҈҉ェ҈҉ル҈҉芸҈҉勉҈҉強҈҉会'

この文字( 後のきゃろさんのLT でわかったのだが、キリル文字で大きい数字を表す際にこの○で数字を囲むらしい。そんな使い方なんだ…。)を消して、本来の状態に戻してみようという問題。 今回の場合、もともとの文字がスペース・アルファベット・数字・カタカナと漢字なので、grepのPerl拡張で地道に対応してやるのが早そうだ。

echo 'jus共催 第38回҈҈҉҈҈҉シ҈҉ェ҈҉ル҈҉芸҈҉勉҈҉強҈҉会' | grep -Po '\p{Katakana}|\p{Han}|[0-9 a-z]' | tr -d \\n ; echo
blacknon@BS-PUB-UBUNTU-01:~$ echo 'jus共催 第38回҈҈҉҈҈҉シ҈҉ェ҈҉ル҈҉芸҈҉勉҈҉強҈҉会'
jus共催 第38回҈҈҉҈҈҉シ҈҉ェ҈҉ル҈҉芸҈҉勉҈҉強҈҉会
blacknon@BS-PUB-UBUNTU-01:~$ echo 'jus共催 第38回҈҈҉҈҈҉シ҈҉ェ҈҉ル҈҉芸҈҉勉҈҉強҈҉会' | grep -Po '\p{Katakana}|\p{Han}|[0-9 a-z]' | tr -d \\n ; echo
jus共催 第38回シェル芸勉強会

後は、対象のUnicode自体を指定して削除してしまうというのもある。

echo 'jus共催 第38回҈҈҉҈҈҉シ҈҉ェ҈҉ル҈҉芸҈҉勉҈҉強҈҉会' | sed -r 's/'$'\u0488''|'$'\u0489''//g'

Q2.

リポジトリ内にある「仏説摩訶般若波羅蜜多心経」ファイルにお経が入ってるので、これに含まれている文字だけを抽出しよう、という問題。 こういう場合、加工したデータをgrepにプロセス置換( hogehoge <(コマンド...) とか、hogehoge >(コマンド...)としてやると、()でくくってるコマンドにファイルとしてデータを渡せる。>の向きには意味があるので注意。)でファイルとして渡してやれば簡単にできる。

echo おのれ、呪ってくれるわ! | grep -o -f <(cat ./仏説摩訶般若波羅蜜多心経|grep -o .)
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ echo おのれ、呪ってくれるわ! | grep -o -f <(cat ./仏説摩訶般若波羅蜜多心経|grep -o .)
呪

ちなみに、お経の中に入ってる漢字を集計するとこんな感じのようだ。

blacknon@BS-PUB-UBUNTU-01:~$ cat ~/ShellGeiData/vol.38/仏説摩訶般若波羅蜜多心経|grep -o .|grep -v 。|sort|uniq -c|sort -k1nr|xargs|sed -r 's/([0-9]+) /\1:/g' #シェル芸
21:無 9:是 9:不 8:羅 7:空 7:故 7:波 6:若 6:呪 6:色 6:多 6:般 5:蜜 4:諦 4:菩 4:亦 4:羯 3:一 3:苦 3:行 3:薩 3:三 3:識 3:切 3:想 3:即 3:提 3:得 3:明 2:依 2:意 2:異 2:界 2:眼 2:子 2:死 2:至 2:舎 2:受 2:諸 2:心 2:尽 2:説 2:大 2:等 2:乃 2:法 2:滅 2:利 2:老 2:
礙 2:罣 1:阿 1:以 1:遠 1:皆 1:観 1:究 1:虚 1:恐 1:経 1:見 1:減 1:五 1:垢 1:香 1:在 1:時 1:耳 1:自 1:実 1:集 1:所 1:除 1:照 1:上 1:浄 1:触 1:深 1:真 1:神 1:身 1:世 1:生 1:声 1:舌 1:僧 1:相 1:増 1:知 1:智 1:中 1:顛 1:度 1:倒 1:道 1:如 1:能 1:婆 1:鼻 1:怖 1:復 1:仏 1:味 1:夢 1:厄 1:有 1:離 1:曰 1:槃 1:涅 1:耨 1:藐 1:蘊 1:訶 1:竟 1:埵

「無」だけで2桁も出てるんだ(´・ω・`)。

Q3.

edoというファイルにある元号の順番を、時代順に並べ替えようという問題。 時代順にするためには外部からデータを取ってくる必要があるので、外部のサイトからWebスクレイピングでデータを取ってくる必要がある。 調べたところ、このサイト がデータとして扱いやすそうだったので利用させてもらう。

htmlをパースするのは面倒なので、w3mを利用してテキストとして利用する。 このサイトだと「~」という文字が含まれるのは元号の箇所だけなので、後はgrepで対象の元号の箇所だけ抜き出せば自動的にソートされた状態になる。

w3m -dump jpnculture.net/gengou/ | grep '~' | grep -o -f <(cat edo)
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ w3m -dump jpnculture.net/gengou/ | grep '~' | grep -o -f edo #シェル芸
元和
元禄
享保
安政

ちなみに、Linuxだとlocaleに和暦のデータが明治6年からはあるのだけど、西暦と連動するようになったのは明治6年からなので、それ以前の和暦は対応しておらずデータが入ってない。 (参考:https://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E3%81%8B%E3%82%89%E8%A5%BF%E6%9A%A6%E3%81%B8%E3%81%AE%E5%A4%89%E6%8F%9B%E8%A1%A8)

流れの中で気になったのが、Rubyだと和暦を扱うライブラリがあるらしい。 へー…知らなかった(´・ω・`)。

Q4.

Excelの横の列(Aから始まる26進数的なやつ。ZまでいくとAAに繰り上がる)を数字に直す、という問題。 結構前に似たようなことをやってたので、それを利用したのだけどブレース展開やxargsを使っててちょっと遅い…(なんで当時このコード書いたのだろう…?)。

eval echo $(echo '{,{a..z}}'{,} '{a..z}'|tr -d ' ')|xargs -n1|awk '!a[$1]++' | cat -n | grep xyz
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ eval echo $(echo '{,{a..z}}'{,} '{a..z}'|tr -d ' ')|xargs -n1|awk '!a[$1]++' | cat -n | grep xyz
 16900  xyz

で、「これは!」と思ったのがPerlでの回答例。 A...XYZでその範囲で出力してくれるという。 便利なんだけど、なんでこんな機能あるんだろPerl。計算も凄い早い。

blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ time (eval echo $(echo '{,{a..z}}'{,} '{a..z}'|tr -d ' ')|xargs -n1|awk '!a[$1]++' | cat -n | grep xyz)
 16900  xyz

real    0m20.987s
user    0m0.376s
sys     0m1.932s
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ time (perl -e 'print "$_\n" for (A...XYZ)' | wc -l)
16900

real    0m0.007s
user    0m0.004s
sys     0m0.000s

Q5.

標準偏差10、平均値0のガウス分布に従う乱数を発生させてください。また、乱数のヒストグラムを描いてください。ヒストグラムは分布の形が分かれば適当で大丈夫です。 …という問題。今までの問題から毛色が変わり、数学の問題になった。統計かなんかで聞いたことがあると思うのだけど、記憶なんて日常で使わないと忘れていくものなわけで…要はなんだかさっぱり(´・ω・`)。

ガウス分布(正規分布)についてはWikipediaを見るとして、偏差値の計算とかのやつかな。 模範解答を見ている限り、ランダムな数字の羅列については小数点以下の桁数が多いので/dev/random等から取得してきて加工するのがいいようだ。

# 統計データの生成(模範解答より)
cat /dev/urandom | \
    tr -dc 0-9 | # 数字のみを取得 \
    fold -b10 | # 10桁の数字に変換 \
    sed 's/^/0./' | # 行の先頭に「0.」を追加して1以下の数字に \
    awk '{a+=$1}NR%12==0{print (a-6)*10;a=0}' | # 12行ごとに集計して、12行の合計から-6した数字を10倍にする \
    head -1000000 > ./data.txt

# グラフの作成(模範解答より)
cat data.txt | \
    awk '{print int(1000+$1)}' | # 全数字に1000を足す \
    sort -n | uniq -c | # ソートと集計  \
    awk '{print $2-1000,int($1/200)}' | # 足した1000を減らして、200で割る \
    awk '{printf("%d\t", $1); for(i=1;i<=$2;i++)printf "*"; print ""}' | # 集計して数を*で表現 \
    grep \* # グラフになる箇所を抽出
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ cat data.txt | \
>     awk '{print int(1000+$1)}' | # 全数字に1000を足す \
>     sort -n | uniq -c | # ソートと集計  \
>     awk '{print $2-1000,int($1/200)}' | # 足した1000を減らして、200で割る \
>     awk '{printf("%d\t", $1); for(i=1;i<=$2;i++)printf "*"; print ""}' | # 集計して数を*で表現 \ >     grep \* # グラフになる箇所を抽出 
-32     *
-31     *
-30     **
-29     ***
-28     ****
-27     *****
-26     *******
-25     *********
-24     ************
-23     ****************
-22     *******************
-21     *************************
-20     ******************************
-19     ************************************
-18     *******************************************
-17     *****************************************************
-16     *************************************************************
-15     ***********************************************************************
-14     **********************************************************************************
-13     *********************************************************************************************
-12     *******************************************************************************************************
-11     *******************************************************************************************************************
-10     *******************************************************************************************************************************
-9      *******************************************************************************************************************************************
-8      ******************************************************************************************************************************************************
-7      ***************************************************************************************************************************************************************
-6      **************************************************************************************************************************************************************************
-5      ***********************************************************************************************************************************************************************************
-4      ****************************************************************************************************************************************************************************************
-3      **********************************************************************************************************************************************************************************************
-2      ***************************************************************************************************************************************************************************************************
-1      ****************************************************************************************************************************************************************************************************
0       ****************************************************************************************************************************************************************************************************
1       **************************************************************************************************************************************************************************************************
2       ***********************************************************************************************************************************************************************************************
3       ***************************************************************************************************************************************************************************************
4       ************************************************************************************************************************************************************************************
5       ***************************************************************************************************************************************************************************
6       ****************************************************************************************************************************************************************
7       *******************************************************************************************************************************************************
8       *******************************************************************************************************************************************
9       *******************************************************************************************************************************
10      ********************************************************************************************************************
11      *********************************************************************************************************
12      ********************************************************************************************
13      **********************************************************************************
14      **********************************************************************
15      *************************************************************
16      ****************************************************
17      ********************************************
18      ************************************
19      ******************************
20      ************************
21      *******************
22      ***************
23      ************
24      *********
25      *******
26      *****
27      ****
28      ***
29      **
30      *
31      *

Q6.

広済寺のホームページから「妙法蓮華経」を取得して、それを先程利用した般若心経と4文字かぶってる箇所を抽出するという問題。 まず般若心経側を4文字、Nグラム(例えば、「あいうえお」から3文字抜き出す場合、「あいう」「いうえ」「うえお」の3パターンになる)にする必要がある。

結構前に響け!ユーフォニアムを利用したシェル芸があったので、それで利用していたコードを使うと楽にできそうだ。

curl -s https://www.kosaiji.org/download/kyoten/myoho_shinji.txt | \
    nkf -w | # utf-8に変換 \
    grep -o -f <(cat 仏説摩訶般若波羅蜜多心経 | sed -r 's/。| /\n/g' | sed ':a;h;s/\(....\).*/\1/p;g;s/.//;ta;d')
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ curl -s https://www.kosaiji.org/download/kyoten/myoho_shinji.txt | \
>     nkf -w | # utf-8に変換 \
>     grep -o -f <(cat 仏説摩訶般若波羅蜜多心経 | sed -r 's/。| /\n/g' | sed ':a;h;s/\(....\).*/\1/p;g;s/.//;ta;d') 
阿耨多羅
三藐三菩
亦復如是
究竟涅槃
得阿耨多
羅三藐三
得阿耨多
羅三藐三
阿耨多羅
...

Q7.

aの2乗(以下、^2) + b^2a*b + 1で割り切れる正の整数の組合せa,bを生成してください(ランダムで良いです)。 また、このとき、(a^2+b^2)/(a*b+1)が正の整数の二乗になっていることを確かめてください。

という問題。 とりあえず、処理は重いのだけどxargsを使ってやってみる。

echo {1..100}\ {1..100} | fmt -1 | xargs -n2 bash -c 'x=$1;y=$2;[ $(( ( $((x**2)) + $((y**2)) ) % $((x*y+1)) )) -eq 0 ] && echo OK:$x,$y' bash
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ echo {1..100}\ {1..100} | fmt -1 | xargs -n2 bash -c 'x=$1;y=$2;[ $(( ( $((x**2)) + $((y**2)) ) % $((x*y+1)) )) -eq 0 ] && echo OK:$x,$y' bash
OK:1,1
OK:2,8
OK:3,27
OK:4,64
OK:8,2
OK:8,30
OK:27,3
OK:30,8
OK:64,4

Q8.

数列x_{i+1} = a * x_i * (1 - x_i)について、xの初期値を0.5としてAWKで出力して、百万回以上連続で、これまで出てきていない数字を出力してください。ただし、aは3以上、4未満の小数から選んでください。これに飽き足らない人は限界に挑戦してください。 …という内容。すでにだいぶ疲れてたので、もうこの頃には問題をよく理解できてなかった状態ではある。

適当にaには0.35とか入れたけど、どうも回答にはならず。

echo|awk '{OFMT="%.20f";x=0.5;a=3.5;for(i=1;i<100;i++){x=a*x*(1-x);print x}}' | sort | uniq -c
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.38$ echo|awk '{OFMT="%.20f";x=0.5;a=3.5;for(i=1;i<100;i++){x=a*x*(1-x);print x}}' | sort | uniq -c
      1 0.38281250000000000000
      1 0.38281967628581853313
      1 0.38281968301106217289
      1 0.38281968301731805759
     18 0.38281968301732416382
      1 0.38281968301751506667
      1 0.38281968322263648652
      1 0.38281990377447178142
      1 0.50088379589339704356
      1 0.50088420992179727431
      1 0.50088421030685925661
     17 0.50088421030721785865
      1 0.50088421030722918292
      1 0.50088421031897323310
      1 0.50088422294386791833
      1 0.50089769484475255013
      1 0.82693481445312500000
      1 0.82694070106983885715
      1 0.82694070658630214332
      1 0.82694070659143370516
     18 0.82694070659143870117
      1 0.82694070659159535364
      1 0.82694070675984865382
      1 0.82694088767001594498
      1 0.87499717950388000709
      1 0.87499726352424933573
      1 0.87499726360239138412
     18 0.87499726360246410373
      1 0.87499726360246643520
      1 0.87499726360484963994
      1 0.87499726616686590575
      1 0.87500000000000000000

模範解答見る限り、もうちょっと違うらしいのだけどこの時点で疲れてたのでその先まではちゃんとやれてない状態。 今度ちゃんとやらないとなぁ…(´・ω・`)。

その後のLTではバックスラッシュエスケープを多用する素数の出力だったり、シェル芸の出力からYoutubeのライブ配信してみたりといったことが行われた。 シェル芸勉強会のLT、内容が結構フリーダムなので、全然知らないことを知る切っ掛けになるので見たことない人は一度見てみると面白いかも。