CodeIQ に掲載されていた各桁総和の問題に挑戦してみました。公式の回答受付が終了したので自分なりの答えをまとめておきます。
ルールは、 各桁総和を取って 1 桁ならその値を返す、複数桁ならもう一度総和をとるというもの。
たとえば
123 → 1+2+3 → 6 9876 → 9+8+7+6 → 30 → 3+0 → 3
という具合。
こうなるように下記のコードの穴を埋めよというもの。
function yourCode() { var arr = []; for (var i = 1; i <= 9999; i ++) { arr.push(-------your codes goes here-------); } return arr; }
とりあえず各 i に対して答えがどうなるかチェック
1->1, 2->2, 3->3, 4->4, 5->5, 6->6, 7->7, 8->8, 9->9, 10->1, 11->2, 12->3, 13->4, 14->5, 15->6, 16->7, 17->8, 18->9, 19->1, 20->2...
といったように 1-9 の繰り返しだということがわかりました。
i を 9 で割った余りである i%9 は 1,2,3,4,5,6,7,8,0,... となります。(i-1)%9 は 0,1,2,3,4,5,6,7,8,... となるためこれらに 1 を足す (i-1)%9+1 は 1,2,3,4,5,6,7,8,9... となってくれますのでこれが答えとなります(9 文字)。
さて、これでは芸がないし、各桁を足して値を求めていないので正攻法で行くコードを考えます。まずは与えられた数字を分割しなければなりませんが、これは正規表現を使えば OK。
任意の一文字ごとに分割する正規表現は/./となります。ドットが任意の一文字で両側のスラッシュはデリミタ(区切り文字)です。また、このままだと1回ヒットした時点で文字分割が止まってしまうので最後まで検索させる g を付加して/./g としましょう。たとえば 1234 に対して、"1234".match(/./g)としてやれば、["1", "2", "3", "4"]という配列が返ってきます。便利ですね。
そうして出来た各配列の隣同士の要素を足し込んでいける便利な関数 reduce() が Array 型にはあります。 Array.reduce(function(firstElem,secoundElem){retunrn firstElem + secoundElem}) という感じで使います。あとはこの値が本当に一桁になっているか確認し、なっていなかったらこれまでの処理を再帰的に行えばいいという具合です。ちょっと複雑でしたが以下のようなコードになりました。
//一応最初に書いたコード +function(){for(a=i;a>9;a=(a+"").match(/./g).reduce(function(x,y){return x-(-y)}));return a;}() //--------わかりやすく書くと----------- (function(){ for(a = i; a > 9; a = String(a).match(/./g).reduce( function(x,y){return parseInt(x)+parseInt(y)}) ); return a; )() //--------全然わかりやすくなってない気がしたので(笑)即時関数の中身をわかりやすく展開------------- for(a=i; a>9;){ //a=iとして、a>9だったら処理の必要あり。そうでなければaが各桁の総和 //各桁の数字を分解した配列。各要素はString型になっていることに注意。 var eachNumber = String(a).match(/./g); //各配列の隣同士の和を取ることを繰り返す。このとき各要素はStringだからparseする。 eachNumber.reduce( function(x,y){ return parseInt(x)+parseInt(y); } } //forループから抜けたので値を返す。 reuturn a;
う~ん、なんか汚いけどまあここらへんにしておきましょう。
以降、1-9 の数列をひたすら吐き出す力技で解きます。まず 1-9 の数の使用を禁じられたので hoge%9 というように周期 9 の数列を作り出すためには何らかの方法で数字の 9 を作らなければなりません。そこで自分で書いてて引いてしまったんですが(i-".".length)%".........".length+".".length というように 9 文字の String の length を参照する形で数を産み出しました。うわぁ、気持ち悪い・・・。まあとりあえずこんな方針で幾つかコードを書いていったら 29 文字まで縮みました。
//答えのパターンいろいろ (i-".".length)%".........".length+".".length (i-!!i)%".........".length+!!i (i-!!i)%(Date.length+!0+!0)+!0 (i-!0)%(Date.length+!0+!0)+!0 (i-!0)%[,,,,,,,,0].length+!0
ちょっとだけ書いておくと-!0 は int 型の-1 になります。また i>0 なので!i は常に 0 に、!!i は常に 1 になります。[,,,,,,,,,]は要素数 9 の配列なので length は 9 を返します。length って文字数多いな...。
0 も+も%も禁じてきました。なんか良い方法があると思いますがもうかったるいので LEVEL2 的解き方を禁止文字を排除して書きなおしていく方針にします。対象のコードは(i-!0)%[,,,,,,,,0].length+!0 としましょう。
まず[,,,,,,,,0]ですが、0 とカンマが使えないのでなんとかしてまた 9 を生み出す方法を考えましょう。ビット演算子を用いて(!!i<<!!i<<!!i<<!!i)-(-!!i)とやると 9 になります。
次に-!0 と+!0 ですが、前者は-true、後者は-(-true)としますか。でも-!!i と-(-!!i)の方が短いですね(1 文字だけど...)。んであとは剰余演算子%を使わずベタに計算するだけです。
最終的にこんなかんじになりました
i - !!i - ((((!!i << !!i) << !!i) << !!i) - -!!i) * parseInt((i - !!i) / ((((!!i << !!i) << !!i) << !!i) - -!!i)) - (-!!i)( //ちょっと改良 i - !!i ) - (typeof z).length * parseInt((i - !!i) / (typeof z).length) - -!!i
ウェブ界隈でエンジニアとして労働活動に励んでいる @gomi_ningen 個人のブログです