〜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 からなる変数の値を変数の名前と見なします。 そしてそこで得られた名前の変数を展開した値を、置換処理の続きで使います。 これが 間接展開 です。