10進展開を計算する問題です。ロジック自体は(1)よりもこちらの方が単純なので、(1)でつまずいたら先にこちらを考えてみるのもいいかもしれません。
さて、まずは10進表記 a_n…a_2a_1 を式で表すと、次のようになります。
10^i-1は先ほど作った関数 pow (あるいは組み込み関数 expt でも)を使えば (pow 10 (- i 1)) で簡単に計算できますね。ということは、あとは summation Σ を表現できればいいわけですね。
再帰ループ
普通の手続き型言語であれば、こういった summation などを表現するために、「繰り返し」構文が用意されています。しかし、 Scheme のような関数型言語では、繰り返し処理を表現するために「再帰」を利用します。(1)でもやりましたね。
番号付きのループを再帰関数でやる例です:
; ループ中の値を確認するための関数
(define (check number symbol)
(begin
(display number)
(display ":")
(display symbol)
(display "\n")
))
; 再帰関数でループする例1
(define (forward-recursive-loop index ls)
(if (null? ls)
(check index 'end)
(begin
(check index (car ls))
(forward-recursive-loop (+ index 1) (cdr ls)) )
))
(forward-recursive-loop 0 '(a b c))
これらを踏まえた上で、digitを作ってみます:
(define (digit-loop ls i)
(if (null? ls)
0
(+ (* (car ls) (pow 10 i))
(digit-loop (cdr ls) (+ i 1)) )
))
(digit-loop '(1 2 3 4) 0)
計算量の最適化
じゃぁこれで完成?いえいえ、不正解です(私は正解を丸々載せる程気前よくありません)。呼び出すとき、入力に余分な 0 を必要としていて、問題の要求を正確に満たしていないからです。じゃぁ引数を合わせて別名の命令を作ればどうかと言うと、それでもまだ半正解(改善の余地あり;所謂△)です。
理論通り計算できているのになぜ半正解なのか疑問に思う人がいるかもしれませんが、計算機科学(そしてプログラミング)では、「計算量」(計算の効率の良さ)を非常に気にするのです。理論数学上は計算可能だからといって、コンピュータで同じものを計算できるかどうかは全く別の問題なのです。
ではどうするのか。実は、(1)でべき乗を計算させる関数を定義させて(2)の誘導になっているのかと思いきや、(2)はべき乗を使う必要がないのです。(ひょっとしたら引っかけ…なのかもしれない。多分そんな意図はない。)
(1)で単純にnをm回掛け合わせるのではなく
と明記されているのは、単純に掛けるアルゴリズムでは計算量が
(i.e. 約 m 回)になるのに対して、指数分解を利用すれば計算量が
(i.e. 約 log_2m 回)と激減するからです。(2)にはそのような指示はないので、上のプログラムでもひょっとすると正解かもしれません。しかし、よく考えてみると何度も 10^i を計算していて無駄が多いと思いませんか? 10^i を計算した後に、 10^i+1 を一から計算し直すのは、無駄が多いですよね?
さて、ここまで来ればもう気づいた人もいるかもしれません。最後にヒントとして次の等式変形を載せておくので、あとは自分で考えてみてください。