元ネタ:Haskellをかける少女
ワイ君のコード
fibo.js
const fibo = n => {
const arr = [0, 1];
for (let i = 2; i <= n; i++) {
arr.push(arr[i - 1] + arr[i - 2]);
}
return arr[n];
};
配列の最後の要素を返す
n !== 0
のとき、arr[n]
は配列の最後の要素なので、そのことが明示的になるようにfiboA
のように書き直してみる。
fiboA.js
const fiboA = n => {
if (n === 0) {
return 0;
}
const arr = [0, 1];
for (let i = 2; i <= n; i++) {
arr.push(arr[i - 1] + arr[i - 2]);
}
return arr[arr.length - 1];
};
配列の最後の要素と、最後から2番目の要素で更新する
また、arr[i - 2]
は最後の要素、arr[i - 1]
は最後から2番目の要素になっているから、下記のfiboB
のように書き直せる。
fiboB.js
const fiboB = n => {
if (n === 0) {
return 0;
}
const arr = [0, 1];
for (let i = 2; i <= n; i++) {
arr.push(arr[arr.length - 2] + arr[arr.length - 1]);
}
return arr[arr.length - 1];
};
配列いらなくね?
最後の要素と最後から2番目の要素しか登場していないので、下記fiboC
のように、それぞれarrLast1
、arrLast2
の変数に書き換えられる。ただし、値の更新には一時変数が必要になる。
fiboC.js
const fiboC = n => {
if (n === 0) {
return 0;
}
let temp;
let arrLast1 = 1;
let arrLast2 = 0;
for (let i = 2; i <= n; i++) {
temp = arrLast1;
arrLast1 = arrLast2 + arrLast1;
arrLast2 = temp;
}
return arrLast1;
};
添字いらなくね?
配列がなくなったため、添字としてのi
は不要になる。n
を直接更新することで、下記のfiboD
ようにループを書き換えられる。
fiboD.js
const fiboD = n => {
if (n === 0) {
return 0;
}
let temp;
let arrLast1 = 1;
let arrLast2 = 0;
for (; n !== 1; n--) {
temp = arrLast1;
arrLast1 = arrLast2 + arrLast1;
arrLast2 = temp;
}
return arrLast1;
};
for
からwhile
へ
fiboE.js
const fiboE = n => {
if (n === 0) {
return 0;
}
let temp;
let arrLast1 = 1;
let arrLast2 = 0;
while (n !== 1) {
n = n - 1;
temp = arrLast1;
arrLast1 = arrLast2 + arrLast1;
arrLast2 = temp;
}
return arrLast1;
};
末尾再起にする
ここまでくれば、下記fiboF
のような末尾再帰に変換できる。計算量を変えないように関数を変換していったので、fiboF
も計算量は$O(n)$になっている。
fiboF.js
const fiboF = (n, arrLast1 = 1, arrLast2 = 0) => n === 0 ? 0 : n === 1 ? arrLast1 : fiboF(n - 1, arrLast2 + arrLast1, arrLast1);
おわり
ちなみに、引数累積するなら下記のコードのほうが短い。
あと、$F_{78}$ < Number.MAX_SAFE_INTEGER
< $F_{79}$ なので、スタック・オーバーフローよりも先に計算誤差が問題になる。
fib.js
const fib = (n, a = 0, b = 1) => n === 0 ? a : fib(n - 1, a + b, a);