【自習】「第42回BLACK HOLEシェル芸勉強会」の問題を解いてみた
問題および解答例へのリンクは次の通りです。
そして、問題で使うファイルはGitHub上のリポジトリにありますので適宜git clone
してください。
また、A6およびA8では画像を生成するためにtextimg
というコマンドを使っています。入手先へのリンクは次の通りです。
A1
$ perl -E 'for $x(1..2){for $y(2..616){for $z(3..617){say "$x $y $z" if ($x<$y&&$y<$z)&&($x+$x*$y+$x*$y*$z==1234)}}}'
1 3 410
1 9 136
2 4 153
2 7 87
2 8 76
2 11 55
2 14 43
2 22 27
解答の考え方自体は簡単なのですが、x、y、z、それぞれの範囲に1から1234をとる総当り戦法で解くといつまで待っても処理が終わらないため、いかにしてループ回数を減らすかがこの問題を解く際のキモになります。
そこでfactor
で1234を素因数分解してみます。
$ factor 1234
1234: 2 617
この結果とx < y < zというあらかじめ与えられている条件から、xの範囲として1か2を、yの範囲として2から616を、そしてzの範囲として3から617までを当てはめればノーストレスで処理を終了させることができます。
いささか乱暴な方法ではありますが、さほど悪くはないと思います。
A2
$ perl -C -nlE 'use utf8;s/[^\P{S}^ ̄]/💩/g;say' oji
あれ(^_^;さのチャン、朝と夜間違えたのかな💩💩⁉俺はまだ起きてますよ〜💩 ちょっと電話できるかナ( ̄ー ̄?)⁉天気悪いと気分もよくないよね💩じゃあ今日は会社休んで 俺とデートしヨウ💩ナンチャッテ💩(笑)💩
perl
の正規表現のUnicodeプロパティを使えば簡単です。
ちなみに、解答内にある[^\P{S}^ ̄]
という正規表現は、シンボルマーク以外・「^」・「 ̄」の否定、すなわち、「^」・「 ̄」を除いたシンボルマークという意味になります。
A3
$ seq 2 20 | factor | awk 'NF==2{printf("%s ",$1);system("matsuya")}NF!=2{print $1}'
2: ミニカレーうどん ミニプレミアム牛めしセット
3: 肉カレーうどん
4:
5: プレミアム旨辛ネギたっぷりネギたっぷりネギたま牛めし
6:
7: 肉カレーうどん
8:
9:
10:
11: 担々うどん
12:
13: オリジナルセット
14:
15:
16:
17: キムカル焼肉定食
18:
19: プレミアム牛めし
20:
seq 2 20
の出力をfactor
に与えると、値が素数である行のフィールド数は2となります。
そして、awk
ではsystem()
関数で外部プログラムを起動できることを利用して、行のフィールド数が2の場合はmatsuya
コマンドを呼び出します。
A4
$ printf '\n\n\n' | wc -l
3
printf
で3行生成し、wc -l
で行数を出力するだけです。
A5
$ $'\154\163'
keikyu_haneda.png narita.jpg oji
$'\<1文字につき1〜3桁の8進値>'
と書くと、その8進値に対応する8ビット文字に置き換えてくれるbash
の機能を利用しました。
bash
のマニュアルにある「クォート」の節を見て思いついた解答です。
A6
例示されているワンライナー
$ echo ✈︎快特羽田空港 | sed -r 's/(.)./\1/' | textimg -F40 | convert - -compress none pbm:- | tail -n +3 | tr -d ' ' | sed -r 's/.{120}/&\n/' | sed -e '1~2s/0/■/g' -e '1~2s/1/🍀/g' -e '2~2,$s/0/□/g' -e '2~2,$s/1/■/g' | xargs -n 2 | tr -d ' ' | textimg > a.png
を実行すると次のような出力が得られます。
そして、飛行機の向きを左右逆にするには、このワンライナーの中にある最初のtr -d ' '
の部分を削除し、全ての行についてピクセルを構成している文字を左端から41マス分左右逆転した状態で並べ直す処理で置き換える必要があります。
この処理を簡単に実現するために、配列の要素を逆順に並べ替える関数を組み込みで持っているスクリプト言語を使います。
次の例では-a
オプションを付けたperl
を使い、特殊配列@F
に格納された各マスの文字について左端の0番目の要素から40番目の要素までを組み込み関数reverse()
で逆順にしたものと41番目以降の要素をあわせて出力するようになっています。
$ echo ✈︎快特羽田空港 | sed -r 's/(.)./\1/' | textimg -F40 | convert - -compress none pbm:- | tail -n +3 | perl -anE 'say reverse(@F[0..40]),@F[41..$#F]' | sed -r 's/.{120}/&\n/' | sed -e '1~2s/0/■/g' -e '1~2s/1/🍀/g' -e '2~2,$s/0/□/g' -e '2~2,$s/1/■/g' | xargs -n 2 | tr -d ' ' | textimg > A6.png
ちなみに、同様の処理をruby
(特殊配列は$F
)で行っているワンライナーは次のようになります。
$ echo ✈︎快特羽田空港 | sed -r 's/(.)./\1/' | textimg -F40 | convert - -compress none pbm:- | tail -n +3 | ruby -ane 'puts [$F[0..40].reverse,$F[41..-1]].join' | sed -r 's/.{120}/&\n/' | sed -e '1~2s/0/■/g' -e '1~2s/1/🍀/g' -e '2~2,$s/0/□/g' -e '2~2,$s/1/■/g' | xargs -n 2 | tr -d ' ' | textimg > A6.png
いずれも出力結果は次のようになります。
A7
$ seq 1000000 | factor | awk 'NF==2{print $2}' | tr \\n ' ' | perl -anE '$prev_len=1;for (0..$#F){$curr_len=length($F[$_]);$l=($prev_len!=$curr_len)?"\n$F[$_] ":"$F[$_] ";print $l;$prev_len=$curr_len}' | sed 's/ $//;$a\'
-a
オプションを付けたperl
を使い、素数を特殊配列@F
の要素に格納します。そして@F
の全要素を対象としたfor
ループ内で1つ前と現在のループ内における@F
の要素にそれぞれ格納されている素数の文字列長を比較し、異なっていればその数字の直前に改行文字を挿入して出力しています。
それでは、問題での指示の通りに、出力を確認してみます。
$ seq 1000000 | factor | awk 'NF==2{print $2}' | tr \\n ' ' | perl -anE '$prev_len=1;for (0..$#F){$curr_len=length($F[$_]);$l=($prev_len!=$curr_len)?"\n$F[$_] ":"$F[$_] ";print $l;$prev_len=$curr_len}' | sed 's/ $//;$a\' | awk '{print $1,$NF}'
2 7
11 97
101 997
1009 9973
10007 99991
100003 999983
どうやら間違いは見当たらないようです。
A8
$ cat <(echo ' 成田空港 ' | textimg -F23 | convert - -compress none pbm:- | awk 'NR>2{print $0"1 1"}') <(echo ' For Narita-Airport ' | textimg -F9 | convert - -compress none pbm:- | awk 'NR>2') | sed -r 's/1 ?/🌑/g;1,25s/0 ?/🍊/g;26,$s/0 ?/🍈/g;25d' | textimg > A8.png
「成田空港」と「For Narita-Airport」のPBMデータを別々にテキスト形式で生成し、これらの切片を縦に結合した後、ピクセルデータ(0:白/1:黒)を適当な色の絵文字に置き換えるという手法で解答してみました。
なお、解答の出力結果は次のようになります。
A6やA8のようにLED行先表示器のような画像をシェル芸で作るというのが、個人的にツボにハマりました。実用性については疑問符を付けざるを得ませんが、問題としては非常に面白く、解いていて楽しかったです。
自習形式の参加ではありましたが、今回も良い頭の体操&ストレス解消になりました。