前置き
今日は第32回シェル芸勉強会のQ7です。
第32回シェル芸勉強会の問題は次のURLにあります。
【問題のみ】jus共催 第32回全くインスタ映えしないシェル芸勉強会 | 上田ブログ
思考を忠実にアウトプットすることを目標にしています。
Q7 問題(上記URLからコピー)
次のファイルについて、次の処理をやってください。
ある数字について、上下左右の数字どれか1つに0が含まれる場合は0、そうでなければ1にする。
次に、上下左右の数字どれか1つに1が含まれる場合は1、そうでなければ0にする。
$ cat image.txt 00010000000000001000000 00000000111111111110000 01111110011111111110001 00011110001111111111000 10011110001111111111000 00000001000000001000000
正解の出力を示します。
00000000000000001000000 00000000001111111100000 00001100011111111110000 00011110001111111111000 00001100000111111110000 00000000000000001000000
解答1
cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]&&a[k1+1][k2]&&a[k1][k2-1]&&a[k1][k2+1])}}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]||a[k1+1][k2]||a[k1][k2-1]||a[k1][k2+1])}}}' | sed 's#.*,##' | tr -d \\n | fold -w 23 | awk 1
解説
とりあえず縦横の数を数えます。
$ cat image.txt | wc 6 6 144 $ cat image.txt | sed 1\!d | grep -o . | wc 23 23 46
どうやら 6x23
のようです。
2次元配列っぽいデータだとワンライナーで処理しづらいので、座標-値に変換します。(以下の例だとCSVライクに出力します)
$ cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | head 1,1,0 1,2,0 1,3,0 1,4,1 1,5,0 1,6,0 1,7,0 1,8,0 1,9,0 1,10,0
で、次の
ある数字について、上下左右の数字どれか1つに0が含まれる場合は0、そうでなければ1にする。
の結果は座標ごとに、
を求めればいいです。
cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]&&a[k1+1][k2]&&a[k1][k2-1]&&a[k1][k2+1])}}}'
ザ・ゴリ押しですが、awk
の2次元配列に入れて(結局2次元配列に入れてる。。。)論理積を取ります。
awk
は未定義変数(a[0][1]
など(1オリジンのため0が未定義))を参照したときに0になるので、この問題としては良くないですが、今回のデータには辺に1が3つ並んでいないので大丈夫です。
最後の
次に、上下左右の数字どれか1つに1が含まれる場合は1、そうでなければ0にする。
も同様にゴリ押しして、整形すれば答えになります。(今回は論理和です。)
$ cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]&&a[k1+1][k2]&&a[k1][k2-1]&&a[k1][k2+1])}}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]||a[k1+1][k2]||a[k1][k2-1]||a[k1][k2+1])}}}' | sed 's#.*,##' | tr -d \\n | fold -w 23 | awk 1 00000000000000001000000 00000000001111111100000 00001100011111111110000 00011110001111111111000 00001100000111111110000 00000000000000001000000
まとめ
awk
によるゴリ押ししか形になりませんでした。
ただ、2次元配列を2回以上パイプするには座標にしておくのが便利でした。
# ホントはtee >(grep -e $((1-1))),$((1)) -e $((1+1))),$((1)) -e $((1))),$((1-1)) -e $((1))),$((1+1)) | cut -d, -f3 ....) | tee ...
をブレース展開で大量に作りたかったですが、最初のgrep
でブレース展開すると座標に戻すにはどうすればいいかわかりませんでした。。。