〜2014年7月中旬〜
aからmを0へ、nからzを1へ変換したいとする。
trでやるならこんな感じになる。
% bash -c 'echo {a..z}' | tr a-z 00000000000001 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1
足りない分は最後の文字が使われるので、
1はまあそれでいいんだけど、0を何とかしたい。
% bash -c 'echo {a..z}' | tr a-m 0 | tr n-z 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1
2回に分けるのはわかりやすいが、man trしてみたら
[CHAR*REPEAT] REPEAT copies of CHAR, REPEAT octal if starting with 0
という記述を発見。つまり
% bash -c 'echo {a..z}' | tr a-z '[0*13]1' 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1
と書けるわけだ。
おいらもやってみた。1問目。
1) Two Bases
Challenge: Find three digits X, Y and Z such that XYZ in base10 (ten) is equal to ZYX in base9 (nine)?
% for i in {1..8}{1..8}{1..8};do [ $[9#$i] = `rev<<<$i` ] && rev<<<$i; done 445
bashとzshならいける。ああ、でも9進数の連番ならこれでいいのか。
% printf "%s\n" {0..8}{0..8}{0..8}|rev|nl -v0|awk '$1==$2""&&$0=$1' 445
9進数の方をreverseしてnl -v0で0からの行番号をつけることで10進数へ変換の代わり、
あとは同じものを表示すればいい。
2問目。
2) One Million
Challenge: Write 1,000,000 as the product of two numbers; neither of which contains any zeroes.
英語だとなんか意味がよくわからないがつまりこういうことか。
% m=1000000;n=$m;while ((m == m/n*n));do : $[n/=2];done; echo $[n*2], $[m/n/2] 15624, 64
tailsさんからご指摘いただいたので修正。
% m=1000000;n=$m;while ((m == m/n*n));do : ${l=n} $[n/=2];done; echo $l, $[m/l] 15625, 64
1問目の9進数は10進の連番から9がつくのを消してもいける。
あとnl使わんでも最終的にはawkなんだからNRを使えばいいだけだった。
% seq -f '%03g' 888 | sed '/9/d' | rev | awk 'NR==$1""&&$0=NR' 445
Challenge: Use the digits 0-9 to create two numbers. What is the highest
product you can achieve when these two numbers are multiplied together?
なんかチートっぽいが、候補となる組み合わせをとりあえず重複ありで表示し、
grepで重複を消して、bcで計算してsortして一番でかいのを取り出してできあがり。
% printf "%s\n" 8{7,6}{5,4}{3,2}{1,0}\*9{7,6}{5,4}{3,2}{1,0} | \ grep -v '\(.\).*\1' | sed 's/.*/print "&=";&/' | bc | sort -s -t= -k2,2n | tail -1 87531*96420=8439739020
Challenge: Arrange the numerals 1-9 into a single fraction that equals exactly
1/3 (one third). No other math symbols wanted; just concatenation some digits
for the numerator, and some to make a denominator.
1から9までの数字を1回だけ使って1/3にする。
9個ということは必然的に4桁と5桁に分割される。
4桁のほうは3倍して桁が上がるのだから3334以上。
1回しか使わないので3456以上。同じように9876以下。
この範囲で見つければいい。
awkで3倍したものと一緒に表示し、grepで重複しているものを除く。
ついでにここで0も除外。うまい具合にはまった。
% seq 3456 9876 | awk '{print $1 "/" ($1*3)}' | grep -v '\(.\).*\1\|0' 5823/17469 5832/17496
問題の作成を
% seq 10000 | paste -sd,
としていたが、pasteは不要で
% seq -s, 10000
でいいのであった。
というわけでawkでの解はこんな感じ。
% seq -s, 10000 | awk 'ORS=NR%100?RS:"\n"' RS=,
しかし
% seq -s, 10000 | sed 's/,/\n/100;P;D'
の解には敵わないな。余計な改行もつかないし。
10000列で100行100列じゃわかりにくいので、100列を10行10列で。
% seq -s, 100 | awk 'ORS=NR%10?RS:"\n"' RS=, 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,32,33,34,35,36,37,38,39,40 41,42,43,44,45,46,47,48,49,50 51,52,53,54,55,56,57,58,59,60 61,62,63,64,65,66,67,68,69,70 71,72,73,74,75,76,77,78,79,80 81,82,83,84,85,86,87,88,89,90 91,92,93,94,95,96,97,98,99,100
最後に余計な空行が入ってしまう。
RS=,にしているので最後の改行がそのまま出てきているわけだ。
単に消すだけならsubを使えばいいが、それもねえ。
そんな難しく考えるまでもなくRSに改行も加えてしまえばいい。
% seq -s, 100 | awk 'ORS=NR%10?",":"\n"' RS='[,\n]' 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,32,33,34,35,36,37,38,39,40 41,42,43,44,45,46,47,48,49,50 51,52,53,54,55,56,57,58,59,60 61,62,63,64,65,66,67,68,69,70 71,72,73,74,75,76,77,78,79,80 81,82,83,84,85,86,87,88,89,90 91,92,93,94,95,96,97,98,99,100
RSが","じゃなくなったので、ORSへは文字列","で。
5)は一体なんなんだろうねえ。引っ掛けかと思った。
というわけで6)のWord Doc。
Challenge: I open up a Word document and type all the numbers 1-10000, separated by spaces, (I did not use any 'thousands' punctuation; just raw numbers). Then, my daughter came along and used search and replace, and changed all the digits '0' into spaces. If I now sum up all the numbers in the document what is the total? (Any number delineated by one or more spaces is a distinct number).
Ubuntuにはnum-utilsというパッケージがあって、
それに含まれるnumsumコマンドを使うと簡単。
% seq -s " " 10000 | tr 0 " " | numsum -r 37359001
trで空白を+にしてbcに食わせるとか、awkで処理するのが一般的だけど、
こんなコマンドもあるということで。
% numsum -h --------------------------------------------------------------------------- numsum : A program that adds up all numbers of input and returns the sum. --------------------------------------------------------------------------- Usage: numsum [options] <file> (Input from a file.) | numsum [options] (Input from command pipeline.) numsum [options] (Input on STDIN. Use Ctrl-D to stop.) Options: -i Only return the integer portion of the final sum. -I Only return the decimal portion of the final sum -c Print out the sum of each column. -r Print out the sum of each row. -x <n> Specify a comma seperated list of columns to print. -y <n> Specify a comma seperated list of rows to print. -s <string> Specify a seperator string for spliting columns. This defaults to consecutive whitespace. -d Debug. For developers only. -h Help: You're looking at it. -V Increase verbosity. -q Quiet mode, don't print any warnings.
なんとなく全部bashで解いてみたらいけた。
% bash -c 'a=({1..10000}); a=${a[@]//0/ }; echo $[${a// /+}]' 37359001
と思ったが、これだと1から10000までを空白区切りになってないとか、
0の繰り返しも1個の空白になるんで題意と違ってしまう。
% bash -c 'a=({1..10000}); a="${a[*]}"; a="${a//0/ }"; echo $[${a// /+}0]' 37359001
こうか。しかしbashも地味に変な機能があるな。
zshは
% zsh -c 'echo $[1++1]' zsh:1: bad math expression: operator expected at `1'
となってしまうので+の繰り返しを1個の+へ変換するなりしないといけない。
最初の例は
a = [*"1".."10000"] a = a.map{|x|x.tr("0", " ")}.join(" ").sub(/ +$/, "") puts eval a.tr(" ", "+")
と同じはずだが、あまりに深すぎてstack level too deep (SystemStackError)で、
Rubyが落ちてしまう。
zshでいうところの
% zsh -c 'foo=bar; bar=1; echo ${(P)foo}' 1
という機能はbashじゃevalとかしないと無理だと思っていたら、
% bash -c 'foo=bar; bar=1; echo ${!foo}' 1
でできると知った。
parameter の最初の文字が感嘆符ならば、変数間接展開が行われます。 bash は残りの parameter からなる変数の値を変数の名前と見なします。 そしてそこで得られた名前の変数を展開した値を、置換処理の続きで使います。 これが 間接展開 です。