第32回シェル芸勉強会 Q4 の解答 + α

前置き

昨日に続いて第32回シェル芸勉強会のQ4を解いていく前に、Q1~3を修正した報告です。

第32回シェル芸勉強会 Q1 の解答 - nogiro_iotaのメモ
第32回シェル芸勉強会 Q2 の解答 - nogiro_iotaのメモ
第32回シェル芸勉強会 Q3 の解答 - nogiro_iotaのメモ

主に、「まとめ」が全くまとめていなかったのを修正しました。
あと、Q3 はちょっとした手違いで最後が突貫工事になってしまって grep -f - <filename>を説明していなかったので追記しました。
grep -f - <filename>:パイプで渡ってきたパターンでファイルをgrepする)

それでは Q4 を解いていきます。

第32回シェル芸勉強会の問題は次のURLにあります。
【問題のみ】jus共催 第32回全くインスタ映えしないシェル芸勉強会 | 上田ブログ

思考を忠実にアウトプットすることを目標にしています。

Q4 問題(上記URLからコピー)

次のデータを

136
725
948

次のように並べ替えてください。

9
7
4
1
2
8
3
5
6

問題を見た感想・補足

問題が何を言ってるのかわからない(あるある)。

問題をよく見ると、どうやら下記の順番に出力するみたいです。

479
258
136

また、入力文字列はGitリポジトリに入っていたnums.txtを使います。

試行1

とりあえず、最終的にキレイに並ぶデータで試します。次のコマンドがワンラインできれいに出ます。

$ echo $'479\n258\n136'
479
258
136

最初に出力されるべき位置が下にあると扱いづらいのでtacで反転します。

echo $'479\n258\n136' | tac
136
258
479

上記の出力の各文字を a_{ij} としたときに、下記の優先順位で a_{i_1j_1}a_{i_2j_2} より優先的に出力されるようです。(左上が原点。a_{00} = 1a_{21} = 8
i_1 + j_1 < i_2 + j_2 のとき
i_1 + j_1 = i_2 + j_2 かつ i_1 < i_2 (≡ j_1 > j_2) のとき
数学できる人ならここから変換できそうだなーと思いつつ、何も思いつかなかったのでお蔵入りです。

解答1

echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#.#sed -n &p <(grep -o . nums.txt | grep \\[0-9\\])#' | bash

解説

やるべきことは、下の順で出力することです。

479
258
136

このままでは扱いづらいので、grep -o .で1列にします。

$ echo $'479\n258\n136' | grep -o .
4
7
9
2
5
8
1
3
6

1は7行目、2は4行目にあって、つまり、行数が出力したい順番になっています。
行数を付けて、出力したい順番の列でソートします。

$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2
7       1
4       2
8       3
1       4
5       5
9       6
2       7
6       8
3       9

左列の順番で出力すると、目的の結果が得られそうです。

$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's# *$#p#'
7p
4p
8p
1p
5p
9p
2p
6p
3p

上記sedに与えてやりましょう。

$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#$#p#' | sed -n -f - <(tr -dc 0-9 < nums.txt | grep -o .)
1
3
6
7
2
5
9
4
8

!!!順番が変わっていません。
(私は)やりがちなミスですが、sedは1行ごとに全スクリプトを適用していくのでこの渡し方では意味がありません。
sedに関わらず、ストリーム指向なプログラム(e.g. grepawk)はだいたいそうです。)

というわけで、まずは脳死とりあえず sh に投げとけ で解きます。
sedsedを含むコマンドを作ります。

$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#.#sed -n &p <(grep -o . nums.txt | grep \\[0-9\\])#'
sed -n 7p <(grep -o . nums.txt | grep \[0-9\])
sed -n 4p <(grep -o . nums.txt | grep \[0-9\])
sed -n 8p <(grep -o . nums.txt | grep \[0-9\])
sed -n 1p <(grep -o . nums.txt | grep \[0-9\])
sed -n 5p <(grep -o . nums.txt | grep \[0-9\])
sed -n 9p <(grep -o . nums.txt | grep \[0-9\])
sed -n 2p <(grep -o . nums.txt | grep \[0-9\])
sed -n 6p <(grep -o . nums.txt | grep \[0-9\])
sed -n 3p <(grep -o . nums.txt | grep \[0-9\])

これを bash(≠sh。プロセス置換<(...)を使っているため。)に渡すと答えが得られます。

$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#.#sed -n &p <(grep -o . nums.txt | grep \\[0-9\\])#' | bash
9
7
4
1
2
8
3
5
6

解答2

echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed -e 's#\(.\).*#print $\1#;1i{' -e '$a}' | awk -f - <(tr -dc 0-9 < nums.txt | sed 's#.#& #g')

解説

nums.txt を最初のechoのに合わせて1列にする意味ないなーと思った結果です。
ワンライナーだと、行の前後を入れ替えるのはけっこうめんどくさいですが、列の入れ替えは簡単です。
そのため、プロセス置換部分が、<(grep -o . nums.txt | grep \[0-9\]) から <(tr -dc 0-9 < nums.txt | sed 's#.#& #g') に変更されています。

sedスクリプトを作っている部分はゴリゴリ書いているだけです。

$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed -e 's#\(.\).*#print $\1#;1i{' -e '$a}'
{
print $7
print $4
print $8
print $1
print $5
print $9
print $2
print $6
print $3
}

余談

試作1を使えば、echo $'479\n258\n136' | grep -o .部分が作れるように思いますが、やり方はわかっていません。

まとめ

問題を見て出力の順番を手で作って、行数と逆変換・出力をしたら解けます。

感想

出力順番と行数の逆変換が思いついたらすぐだった。


メモ

  • Q1: 14679をtrなどの引数にする
  • Q2: xxd・Q1の解答から出発しない
  • Q3: 先に素数を計算する

この解答たちから(ワンライナー用)コマンド部品の逆引き辞典を作ったら便利なのでは?