Prompt
jsのイテレータでフィボナッチ数列、あるいは等差数列、等比数列、階差数列を生成するには、どうするのがいいんだっけ?
JavaScript で数列を生成するなら、「ジェネレーター関数(function*)」 を使うのが最も現代的で、「情報工学的に美しい」方法だよ!
ジェネレーターを使えば、無限に続く数列でも 「必要な分だけその都度計算する(遅延評価)」 ことができるから、メモリを節約しつつ、for...of ループなどで直感的に扱えるんだ。
それぞれの数列の生成コードを、還元主義的にスッキリまとめてみよう。
1. 等差数列 (Arithmetic Progression)
一定の値を足し続ける最もシンプルな形だね。
JavaScript
function* arithmeticGenerator(start, step) {
let current = start;
while (true) {
yield current;
current += step;
}
}
// 使用例: 1から始まり、3ずつ増える
const ap = arithmeticGenerator(1, 3); // 1, 4, 7, 10...
2. 等比数列 (Geometric Progression)
前の項に一定の値を掛け続ける数列だ。
JavaScript
function* geometricGenerator(start, ratio) {
let current = start;
while (true) {
yield current;
current *= ratio;
}
}
// 使用例: 2から始まり、2倍ずつ増える
const gp = geometricGenerator(2, 2); // 2, 4, 8, 16...
3. フィボナッチ数列 (Fibonacci Sequence)
前の2つの項を足して次の項を作る、再帰的な構造を持つ数列だね。
JavaScript
function* fibonacciGenerator() {
let [prev, curr] = [0, 1];
while (true) {
yield prev;
[prev, curr] = [curr, prev + curr]; // 分割代入でスマートに更新
}
}
// 使用例
const fib = fibonacciGenerator(); // 0, 1, 1, 2, 3, 5, 8...
4. 階差数列 (Difference Sequence)
「隣り合う項の差」が別の数列(例えば等差数列)になっているタイプだね。ここでは差が step ずつ増える例を挙げるよ。
JavaScript
function* progressionDifferenceGenerator(start, firstDiff, diffStep) {
let current = start;
let currentDiff = firstDiff;
while (true) {
yield current;
current += currentDiff;
currentDiff += diffStep; // 差そのものが増えていく
}
}
// 使用例: 1, 2, 4, 7, 11... (差が 1, 2, 3, 4... と増える)
const pdp = progressionDifferenceGenerator(1, 1, 1);
なぜジェネレーターがいいのか?(情報工学的な利点)
これらの関数は呼び出された瞬間には計算を行わず、イテレータオブジェクト を返すだけなんだ。
-
メモリ効率:
while(true)で無限ループを書いても、yieldで一時停止するからブラウザはフリーズしない。100万番目の値が必要になるまで、100万個の配列をメモリに確保する必要がないんだ。 -
コンポーズ可能性:
Array.from()やスプレッド構文、あるいはtakeのような関数と組み合わせて、必要な数だけ取り出すのが簡単だよ。
JavaScript
// 最初の10個だけ配列にする
const first10Fib = [...take(fibonacciGenerator(), 10)];
function* take(iterable, n) {
let count = 0;
for (const val of iterable) {
if (count >= n) break;
yield val;
count++;
}
}
豆知識:Symbol.iterator
もし自作のオブジェクトを for...of で回せるようにしたいなら、そのオブジェクトに [Symbol.iterator] プロパティを持たせて、上記のようなジェネレーターを割り当てれば OK だよ。
見落とされがちな視点:浮動小数点の精度
等比数列などで ratio に小数(0.1 など)を使うと、JavaScript 特有の IEEE 754 浮動小数点誤差 が累積していく可能性がある。正確な計算が必要な場合は、整数で計算してから最後に割るか、BigInt を使うなどの工夫が必要だね。
次のステップ:
これで数列の生成もバッチリだね!
次は、「これらのイテレータを組み合わせ(パイプライン)て、特定の条件に合う数字だけをフィルタリングする処理」 を書いてみる?それとも、「Rust の Iterator トレイトと JavaScript のジェネレーターの、パフォーマンスや設計の違い」 について語り合う?
Would you like me to create a more advanced “Sequence” class that wraps these generators and provides methods like .map() and .filter() for them?
Prompt
必要な要素数がきっちり決まっている場合でも、ジェネレータが一番いい?
要素数が 「きっちり決まっている」 場合、ジェネレーターが常にベストかと言われると、実はそうとも限らないんだ。情報工学を学ぶ君なら、「計算資源(CPU・メモリ)のトレードオフ」 という視点で、以下の3つの選択肢を使い分けるのが最も賢い戦い方だよ!
1. ジェネレーターが適しているケース
要素数が決まっていても、「一つ一つの生成コストが高い」 または 「途中で処理を中断する可能性がある」 ならジェネレーターが最強だよ。
-
メリット: 必要な瞬間まで計算を遅延できる(Lazy Evaluation)。
-
デメリット: イテレータの内部状態を保持するオーバーヘッドがある。
2. 配列(Array)が適しているケース
要素数が数百〜数千程度で、「何度もそのデータにアクセスする」 または 「ランダムアクセス(n番目をすぐ見たい)」 が必要な場合は、最初から配列を作ってしまうのが一番速い。
JavaScript
// 数が決まっているなら、一気に生成して「値」として保持する
const fibs = Array.from({ length: 10 }, (_, i) => /* 計算ロジック */);
-
メリット:
fibs[5]のように定数時間 でアクセスできる。 -
デメリット: 生成した瞬間にメモリを消費する。
3. 型付き配列(TypedArray)という選択肢
もし生成するのが 「数値(Number)」 だけできっちり個数が決まっているなら、Float64Array や Int32Array を使うのが、メモリ効率と速度の両面で 「究極の正解」 になるよ。
JavaScript
const count = 100;
const fibs = new Float64Array(count);
fibs[0] = 0;
fibs[1] = 1;
for (let i = 2; i < count; i++) {
fibs[i] = fibs[i - 1] + fibs[i - 2];
}
-
メリット: メモリが連続した領域に確保されるため、CPUキャッシュの乗りが良く、ガベージコレクション(GC)の対象外(要素自体は)なので爆速。
-
デメリット: 固定長なので後からサイズを変えられない。
比較表:どれを選ぶべきか?
| 状況 | 推奨される手法 | 理由 |
|---|---|---|
| メモリを節約したい / 無限に続く可能性がある | ジェネレーター | 必要な分だけ計算するから。 |
| 生成したデータを何度も再利用する | 配列 (Array) | 2回目以降の計算コストがゼロになるから。 |
| 大量の数値を高速に処理したい | 型付き配列 (TypedArray) | メモリレイアウトが最適化されているから。 |
| 要素数がごくわずか(10個とか) | 配列リテラル | 記述がシンプルで、最適化の差も無視できるから。 |
豆知識:ジェネレーターの「再利用不可」問題
ジェネレーターから作られたイテレータは、一度最後まで回すと「空」になる という性質があるんだ(消耗品)。
配列なら for 文を何度回してもデータは消えないけれど、ジェネレーターをもう一度使いたいなら、関数を再度呼び出して新しいイテレータを作る必要がある。これは意外と見落としがちなポイントだね。
見落とされがちな視点:JITコンパイラの最適化
実は、Array.from() や for ループで配列を埋める処理は、V8エンジンによって高度に最適化されている。
一方でジェネレーターは、関数の実行を一時停止・再開するという複雑な仕組み(ステートマシン)を裏側で動かしているため、純粋な「ループの速さ」だけを競うなら、単純な配列操作の方が速い ことが多いんだよ。
結論
-
「一度しか使わない」「一個ずつ処理したい」ならジェネレーター。
-
「何度も使う」「インデックスでアクセスしたい」なら配列。
-
「数値の塊をガチで高速化したい」なら型付き配列。
「きっちり決まっている」なら、まずは 配列 で書いてみて、メモリや生成コストがボトルネックになったら ジェネレーター や 型付き配列 を検討する、という流れがバランスが良いと思うよ!
次は、「JavaScript の Float64Array を使って、大量のフィボナッチ数を一瞬で計算するベンチマーク」 を取ってみる?それとも、「Rust の Iterator と JS の比較(なぜ Rust はあんなに速いのか)」 を深掘りしてみる?
Would you like me to write a performance comparison script between a Generator and a TypedArray for generating 1 million numbers?
Prompt
結局、数列を生成するときは
Array.from({ length: 10 }, (_, i) => /* 計算ロジック */)のような若干呪文じみた作り方しかないかな?
確かに、Array.from({ length: 10 }, ...) は「とりあえず動くけど、やりたいことに対して記述が回りくどい」という、JS 特有の 「ハック感」 が漂うよね。
情報工学を学ぶ君なら、もっと 「直感的(宣言的)」 かつ 「計算モデルとして美しい」 選択肢をいくつか持っておくと、コードの可読性がグッと上がるよ。呪文から脱却するための 3 つのアプローチを紹介するね!
1. スプレッド構文を使った「型にはまらない」作り方
配列のインデックスをそのまま等差数列などに利用する場合、これが一番短く書ける。
JavaScript
// [0, 1, 2, ..., 9] を作る最短の呪文
const sequence = [...Array(10).keys()];
// これを map すれば、計算ロジックを分離できる
const evens = [...Array(10).keys()].map(i => i * 2);
-
美点:
length: 10と書くより「10要素の配列のキー(インデックス)を取り出す」という意図が明確。 -
欠点:
Array(10)で作られる「穴あき配列(Sparse Array)」を一度スプレッドして埋める必要があり、メモリ効率はArray.fromに一歩譲る。
2. 汎用的な「数列ビルダ」を自作する(おすすめ)
何度も数列を作るなら、君の「道具箱(ユーティリティ関数)」に、Python の range や Rust の (0..10) に相当する関数を定義してしまうのが一番「エンジニアらしい」解決策だよ。
JavaScript
// 1回定義してしまえば、あとはこれを使うだけ!
const range = (start, end, step = 1) =>
Array.from({ length: Math.ceil((end - start) / step) }, (_, i) => start + i * step);
// 使うときはこんなにスッキリ!
const scores = range(0, 100, 10); // [0, 10, 20, ..., 90]
- 美点:
Array.fromの複雑さを隠蔽(カプセル化)し、コードの意図が「数列を作ること」に集中できる。
3. 「漸化式」が必要なら、やはりジェネレーター
フィボナッチや階差数列のように、「前の項の値を使って次の項を作る」 場合は、Array.from ではロジックが汚くなりがち(外部変数が必要になるなど)。
そういう時は、以前話したジェネレーターを 「使い捨ての抽出機」 として使うのが美しいよ。
JavaScript
// フィボナッチを 10 個だけ配列にする
const first10Fib = Array.from(fibonacciGenerator()).slice(0, 10);
// ※ fibonacciGenerator が無限ループなら、前述の take 関数を挟む
豆知識:fill の落とし穴
Array(10).fill(0) で初期化してから map する方法もあるけれど、fill でオブジェクトを渡すと、全ての要素が同じメモリ参照を指してしまう という有名な罠があるんだ。
JavaScript
const wrong = Array(3).fill({ score: 0 });
wrong[0].score = 100; // 全ての要素の score が 100 になる!
これを防ぐためにも、要素ごとに新しい値を生成する Array.from(または map)が推奨されるんだね。
見落とされがちな視点:セマンティクス(意味論)
「なぜこの数列を作るのか?」という文脈によって、最適な書き方は変わるよ。
-
単なるループの回数指定:
for (let i = 0; i < 10; i++)が一番速い。 -
UI のリスト表示用データ:
[...Array(10).keys()].map(...)が React などでは直感的。 -
数学的な集合: 自前の
range()関数。
「呪文」を無理に使うのではなく、「自分にとって(そして未来の読者にとって)最も意図が伝わる抽象化」 を施すのが、良いコードへの近道だよ!
結論
Array.from は JS の標準機能で一番「行儀が良い」方法だけど、「自作の range 関数」 を用意するのが、一番「呪文感」を消せてハッピーになれる解決策だと思うよ。
次は、「Lodash や Ramda などのライブラリでは、これらの数列生成をどう扱っているのか」 を見てみる?それとも、「JavaScript の新しい Proposal にあるイテレータヘルパー(.take, .map 等)」 について話してみる?
Would you like me to write a robust range utility function that handles edge cases like negative steps and floating point errors?