概要
データ構造とは「値をしまう箱」ではない。
それは**“意味・操作・性能・制約”の全てを包括する設計構造**である。
JavaScriptでは何でも配列・何でもオブジェクトで済まされがちだが、
本当に意味を持つ構造を選び、適切な操作で処理することがパフォーマンスと保守性の鍵となる。
本稿では、構造・操作・計算量を意識した戦略的なデータ設計とアルゴリズム選定法を解説する。
1. 配列(Array) vs Set:重複と検索の違い
const arr = ['a', 'b', 'a'];
const set = new Set(arr); // → {'a', 'b'}
操作 | Array | Set |
---|---|---|
重複 | 許容 | 自動排除 |
含むか? | includes() |
has() |
挿入 | push() |
add() |
✅ セット集合的な意味(タグ一覧など)は Set
が明確で高速
2. オブジェクト vs Map:キーの型と順序の違い
const map = new Map();
map.set({ id: 1 }, 'value'); // OK
const obj = {};
obj[{ id: 1 }] = 'value'; // ❌ "[object Object]"
特性 | Object | Map |
---|---|---|
キーの型 | 文字列のみ | 任意の型(オブジェクト含む) |
順序保証 | なし | 挿入順に保持 |
イテレーション | for...in | map.forEach / entries |
✅ データ → メタ情報のマッピングには Map
が正しい構造
3. 計算量に基づく操作選定(Big-O)
操作 | Array | Set / Map |
---|---|---|
挿入 | O(1) | O(1) |
検索 | O(n) | O(1) |
削除 | O(n) | O(1) |
→ ✅ 検索/存在判定/削除が多い構造はMap/Setに置き換え可能かを常に検討
4. 配列操作の最適化と破壊的変更の回避
❌ 非イミュータブルな更新
arr.splice(1, 1); // 破壊的
✅ イミュータブルな構造で再生成
const updated = arr.filter((_, i) => i !== 1);
- ✅ UIと状態の一貫性を保証するため、状態変更は常に非破壊的に
5. 構造変換のユーティリティ化(構造はロジックにしない)
function arrayToMap(items, keyFn) {
const map = new Map();
for (const item of items) {
map.set(keyFn(item), item);
}
return map;
}
- ✅ 配列 → Map変換を構造的に記述
- ✅ 変換処理 = 意図のある変換として明示
6. ストリーム的操作と遅延評価の選択肢
const result = [1, 2, 3, 4, 5]
.map(x => x * 2)
.filter(x => x > 5);
- ✅ チェーン可能な処理構造は可読性が高く意味が明確
- ❌ 重い処理には
for...of
で早期breakを意識
設計判断フロー
① 重複排除や存在判定が多い → Setを選ぶ
② キーがオブジェクト or 順序が必要 → Mapが妥当
③ 配列操作で毎回filter/mapしていないか? → 計算量を確認
④ 状態更新が破壊的か? → イミュータブルに書き換えられるか
⑤ データ変換ロジックが散乱していないか? → ユーティリティとして再利用可能に
よくあるミスと対策
❌ 配列.includesでO(n)検索を毎回行ってパフォーマンス劣化
→ ✅ Set
に置き換えることで**検索がO(1)**に
❌ Mapの代わりにオブジェクトを使って複雑なキーが壊れる
→ ✅ Map
は非プリミティブキー対応 & 順序保証付き
❌ 配列に対する破壊的更新でUIステートが壊れる
→ ✅ filter/map/slice
で非破壊な操作を常に優先
結語
データ構造とは「どう持つか」ではない。
それは**“どう使うか、どう守るか、どう制御するか”を含めた設計戦略**である。
- 意味に沿った構造を選び
- 操作の意図を構文に込め
- 計算量に根拠を持ち
- 状態を壊さず設計する
JavaScriptにおけるデータ構造とアルゴリズム設計とは、
“書き方の違いではなく、拡張性と性能を決める構造的な選択”である。