多重操作メソッド VS for
他の言語でもそうですが、これらの配列操作のメソッドの方がforEachよりも速いのですか?
実は逆で、forEach のほうが速いことが多いです。
flatMap や filter や map をチェーンすると、各ステップで中間配列が新たに生成されるためメモリとCPUのオーバーヘッドが増えます。
|
1 2 3 4 |
// この書き方は内部で3つの中間配列を生成する clearedRows.flat() // → 新しい配列① .filter(cell => ...) // → 新しい配列② .forEach(cell => ...) |
|
1 2 3 4 5 6 7 8 |
// こちらのほうが配列生成ゼロで速い for (const row of clearedRows) { for (const cell of row) { if (cell && cell.elLevel === 3) { elementGroupManager.removeId(cell.groupId, pBoard); } } } |
ただし実用上はどちらでも問題ないことがほとんどです。
テトリスのボードサイズ(20×10 = 200セル)程度では差はナノ秒レベルで、人間には感知できません。パフォーマンスが問題になるのは数万〜数百万件のデータを扱う場合です。
なので選択基準としては:
- 読みやすさ・意図の明確さ →
flatMap/filterチェーン - パフォーマンスが最優先 →
for...ofやforEach
が一般的な使い分けです。
配列.flat()
JavaScriptの.flat()`メソッドは、多次元配列(ネストされた配列)を、指定した深さまで平坦化(1次元の配列に平らにならす)した新しい配列を返す機能です。引数を省略すると、初期値として1階層だけ平坦化され、元の配列は変更されない(非破壊)という特徴があります。
flat()メソッドの主な効果・特徴
- ネストを解消(平坦化):
[[1, 2], [3, 4]]が[1, 2, 3, 4]のように、入れ子構造を解除します。 - 深さの指定:
flat(1)(既定値)は1階層のみ、flat(2)は2階層下まで平坦化します。 - 深さ無限:
flat(Infinity)を指定すると、ネストがどれほど深くても完全に1次元の配列にできます。 - 空要素の削除: 配列内の空のスロット(疎配列)は削除されます。
- 新規配列の作成: 元の配列を変更せず、新しい配列を返します(シャローコピー)。
使用例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 1. 基本的な2次元配列の平坦化 (1階層) const nested = [1, 2, [3, 4]]; console.log(nested.flat()); // [1, 2, 3, 4] // 2. 深いネストの平坦化 (2階層) const deepNested = [1, 2, [3, 4, [5, 6]]]; console.log(deepNested.flat(2)); // [1, 2, 3, 4, 5, 6] // 3. 無限に平坦化 console.log([1, [2, [3, [4]]]].flat(Infinity)); // [1, 2, 3, 4] // 4. 空要素の除去 const sparse = [1, 2, , 4, [5, , 6]]; console.log(sparse.flat()); // [1, 2, 4, 5, 6] |
関連メソッド・注意点
flatMap():map()(変換)とflat()(平坦化)を同時に実行します。マッピング後に配列を平坦化したい場合に最適です。- 非破壊的:
flat()は新しい配列を返します。 - 対応ブラウザ: ES2019で追加された機能で、現代のブラウザではほぼすべて対応しています。
配列内の配列の要素を一度に取り出したり、多次元配列の要素数を数えたりする際に非常に便利です。
配列.filter()
配列内の各要素に対して指定した条件(コールバック関数)をテストし、真(true)を返した要素のみを抽出して新しい配列を作成します。元の配列は変更されません。
基本的な使い方
|
1 2 3 4 5 6 |
const numbers = [10, 5, 20, 8, 15]; // 10より大きい数値だけを抽出 const filteredNumbers = numbers.filter(number => number > 10); console.log(filteredNumbers); // 出力: [20, 15] console.log(numbers); // 元の配列はそのまま: [10, 5, 20, 8, 15] |
構文
|
1 2 3 |
const newArray = array.filter(callback(element, index, array) { // true を返した要素が残る }); |
element: 配列の現在の要素。index (オプション): 現在の要素のインデックス。array (オプション): filterが呼び出された配列
実践的な活用例
1. オブジェクトの配列から特定の条件に合うものを抽出
|
1 2 3 4 5 6 7 8 |
const users = [ { id: 1, name: "Alice", active: true }, { id: 2, name: "Bob", active: false }, { id: 3, name: "Charlie", active: true } ]; // アクティブなユーザーのみ抽出 const activeUsers = users.filter(user => user.active); // [{ id: 1, name: "Alice", active: true }, { id: 3, name: "Charlie", active: true }] |
2. 配列から不要なデータを削除
|
1 2 3 4 |
const items = ["apple", "", "banana", null, "orange"]; // 空文字やnullを除外 const cleanedItems = items.filter(item => item); // ["apple", "banana", "orange"] |
3. 数値の範囲でフィルタリング
|
1 2 3 4 |
const scores = [50, 80, 95, 60, 40]; // 70点以上の数値を取得 const passScores = scores.filter(score => score >= 70); // [80, 95] |
注意点
- 空配列: 条件に一致する要素がなかった場合、空の配列
[]が返されます。 - シャローコピー: 抽出された配列は、元の配列の要素の「シャローコピー(参照)」です。オブジェクトのプロパティを変更すると、元のオブジェクトも変更されます。
.map()
JavaScriptの.map()メソッドは、配列の全要素に対して指定した関数を適用し、その結果から新しい配列を作成する(元の配列は変更しない)高階関数です。データを変換・加工して新しいリストを生成する際に、for文よりも簡潔で宣言的に記述できる効果があります。
主な効果と特徴
- 非破壊的なデータ変換: 元の配列を変更せず、加工された新しい配列を返します。
- コードの簡潔化: for文で配列を回してpushする手間が省け、コードが見やすくなります。
- 要素ごとの一括処理: すべての要素に同じ処理を一度に適用できます。
基本構文とコード例
|
1 |
const newArray = oldArray.map(callbackFunction); |
|
1 2 3 4 |
// 例:数値の配列を2倍にする const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); // doubledは [2, 4, 6]、numbersは [1, 2, 3] のまま |
forEachメソッドとの違い
- map(): 新しい配列を返す。
- forEach(): 何も返さない(処理のみ実行する)
データ操作やReactなどのフロントエンド開発で、配列をUIの要素(JSX)に変換する際によく使用されます。
配列1.push([…配列2[0])]
「配列2の最初の要素(これも配列である想定)の中身をばらして、配列1の末尾に個別に追加する」という意味になります。
具体的には、破壊的な(元の配列を変更する)要素追加操作です。
1. 意味の分解
配列2[0]: 配列2の最初の要素(配列である必要がある)を参照。[...配列2[0]]: スプレッド構文(...)を使い、配列2[0]の内容を展開して、新しい配列の中に詰め込む。push(...): 展開された要素を、配列1の末尾に追加する。
2. 具体的な動作例
|
1 2 3 4 5 6 7 |
let 配列1 = ["A", "B"]; let 配列2 = [["X", "Y"], "Z"]; // 配列2[0]は ["X", "Y"] // 配列2[0]の要素("X"と"Y")をバラして追加 配列1.push(...配列2[0]); console.log(配列1); // 出力: ["A", "B", "X", "Y"] |
3. なぜ [...配列2[0]] のような記述が必要か?
もし単に 配列1.push(配列2[0]) と書いた場合、配列1の中に、配列2[0](配列そのもの)が「1つの要素」として入ってしまうため、二次元配列(ネストした配列)になります。
|
1 2 3 4 5 6 |
// 間違った例(要素をばらしたくない場合を除く) let 配列1 = ["A", "B"]; let 配列2 = [["X", "Y"]]; 配列1.push(配列2[0]); console.log(配列1); // 出力: ["A", "B", ["X", "Y"]] |
4. まとめ
このコードは、「配列の中にある配列を、別の配列に平坦化して追加したい」場合によく使われるテクニックです。
注意点
配列2[0]が配列でない場合、エラーになります。pushは元の配列(配列1)を変更(破壊)します。
同様の動作は 配列1.push.apply(配列1, 配列2[0]) でも可能ですが、スプレッド構文(...)の方がモダンで読みやすいとされています。
配列.every()
JavaScriptのarray.every(callbackFn)メソッドは、配列の全要素が指定された条件(コールバック関数)を満たしているかチェックし、すべて満たせばtrue、1つでも満たさなければfalseを返す便利なメソッドです。
空の配列に対しては常にtrueを返します。
()内の説明(引数)
every()のカッコ内には、各要素をテストする関数(コールバック関数)を渡します。
|
1 |
array.every((element, index, array) => { /* テスト内容 */ }); |
1. callbackFn (必須)
配列の各要素に対して実行される関数です。この関数がtrue(真値)かfalse(偽値)を返すことで判定が行われます。
element: 現在処理されている配列の要素。index(オプション): 現在処理されている要素のインデックス。array(オプション):every()が呼び出された配列そのもの。
2. thisArg (オプション)
callbackFnを実行する際にthisとして使用する値です。
具体的な使用例
例:すべての数字が10より大きいかチェックする
|
1 2 3 4 |
const numbers = [12, 15, 20, 25]; const allOverTen = numbers.every(value => value > 10); console.log(allOverTen); // true (すべて10より大きいため) |
例:1つでも条件に合わない場合
|
1 2 3 4 |
const numbers = [12, 5, 20]; const allOverTen = numbers.every(value => value > 10); console.log(allOverTen); // false (5が含まれているため) |
特徴
- 途中停止: 条件を満たさない要素が見つかった時点で、残りの要素は処理されず、即座に
falseを返します。 - 空配列:
[].every(...)はtrueを返します。 - 読み方: “エブリ”と呼びます。
よく使われる関連メソッド
- some(): 1つでも条件を満たすかチェックする(1つでも真なら
true)。 - filter(): 条件を満たす要素だけを抽出する。
- map(): すべての要素を変換する
配列.some()
「内部配列(子配列)の少なくとも1つが条件を満たしているか」をチェックし、trueかfalseを返す効果があります。1つでも条件に合致する子配列を見つけた時点で即座に処理を終了(ショートサーキット)するため、効率的に検索が可能です
1. 二次元配列.some()の動作原理
二次元配列において、.some()は親配列の要素である「子配列」を順番にコールバック関数へ渡します。
|
1 2 3 4 5 6 7 8 9 |
const matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; // 「1つの子配列の中に5という値が含まれているか」をチェック const hasFive = matrix.some(row => row.includes(5)); console.log(hasFive); // true |
2. 主な活用事例
① 2次元配列内の特定の要素の存在確認
「いずれかの行(子配列)に、特定の要素が含まれているか」のチェックに最適です。
|
1 2 3 4 5 6 7 |
const table = [ ['A', 'B'], ['C', 'D'], ['E', 'F'] ]; // 「D」が含まれる行があるか const hasD = table.some(row => row.includes('D')); // true |
② 2次元配列内での条件判定
「特定の条件を満たす行が、1つでも存在するか」を判定します。
|
1 2 3 4 5 6 7 |
const data = [ [10, 20], [30, 40], [50, 60] ]; // 合計が100を超える行が1つでもあるか const isLarge = data.some(row => (row[0] + row[1]) > 100); // false |
3. 特徴と注意点
- 戻り値: 条件に合致する要素が1つでもあれば
true、なければfalse。 - 高速性: 条件に合致した時点で走査をストップするため、大きな配列でも不要なループが省略されます。
- 空の配列: 空の配列に対して実行すると、必ず
falseを返します。
二次元配列のすべての要素が条件を満たすかを確認したい場合は、.some()の代わりに.every()メソッドを使用します。
reduce()
配列の全要素にコールバック関数を適用し、最終的に単一の累積値(数値、文字列、オブジェクトなど)を返す高機能な配列操作メソッドです。配列を左から右へ順に処理し、合計算出、データ集計、配列からオブジェクトの生成など、複雑なデータを変換・圧縮する際に効果的です。
reduceメソッドの主な効果・用途
- 配列の合計値・積の計算: 配列内の数値をすべて合計する際、最もシンプルに記述できます。
- データの集計・グループ化: オブジェクトの配列から、特定のプロパティ(カテゴリー等)ごとに合計値や配列を作成できます。
- 配列から別のデータ構造の生成: 配列を元に、新しいオブジェクトや連想配列を作成する際、mapやfilterを組み合わせるよりも効率的です。
- 高度なデータ変換: データ構造を複雑に変換する際、一度のループで処理を完結させることができます
基本的な構文
|
1 2 3 4 |
array.reduce((accumulator, currentValue) => { // 処理内容 return accumulator + currentValue; }, initialValue); |
accumulator (累積値): コールバックの戻り値を累積していく値。currentValue (現在の値): 現在処理されている配列の要素。initialValue (初期値): 最初にaccumulatorに渡す値(設定が推奨されます)
具体的な利用例
1. 数値の合計
|
1 2 |
const numbers = [10, 20, 30]; const sum = numbers.reduce((acc, curr) => acc + curr, 0); // 60 |
2. オブジェクトの集計
|
1 2 |
const items = [{ name: 'A', price: 100 }, { name: 'B', price: 200 }]; const total = items.reduce((acc, curr) => acc + curr.price, 0); // 300 |
reduceを活用することで、ループ処理を記述することなく、より関数型プログラミングスタイルで簡潔に配列を処理できます。
new Set()
「重複しない値の集まり(集合)」を作るためのオブジェクトです。
配列(Array)に似ていますが、「同じ値は一つしか持てない」という点が最大の特徴です。
主な効果と使い道は以下の通りです。
1. 配列から重複を排除する
配列を Set に渡し、再び配列に戻すだけで、重複要素を簡単に削除できます。
|
1 2 |
const numbers = [1, 2, 2, 3, 4, 4, 5]; const uniqueNumbers = [...new Set(numbers)]; // [1, 2, 3, 4, 5] |
2. 値の存在チェックが高速(パフォーマンス向上)
配列の includes() は要素数が増えるほど時間がかかります(線形探索)が、Set の has() メソッドは、内部的に最適化されているため、非常に高速に値を検索できます。
大量のデータから特定の値があるか何度も確認する処理に向いています。
3. 「一意なデータ」であることを明示できる
コード内で Set を使うことで、後から読む人に対して「このデータ群に重複は存在しない」という意図を明確に伝えることができます。
4. 集合演算(和・積・差)が標準で可能
最新のブラウザ環境では、2つの Set 同士を比較して、共通部分(積集合)や差分(差集合)を求めるメソッド(例: intersection(), difference() など)が標準で利用可能です。
基本的な使い方
- 作成:
const mySet = new Set(); - 追加:
mySet.add(値);(既にある値なら無視される) - 確認:
mySet.has(値);(あればtrue) - 削除:
mySet.delete(値); - 個数:
mySet.size;(配列のlengthに相当)
これは二次元配列にも使用できますか?
二次元配列(配列の中に配列がある状態)に対しても new Set() を使うことはできますが、期待通りに「中身が同じ配列」を重複として削除することはそのままではできません。
理由は、JavaScript の Set が「値」ではなく、メモリ上の「参照(アドレス)」で重複を判断するからです
なぜうまくいかないのか?
たとえ中身が同じ [1, 2] であっても、プログラム上ではそれぞれ「別の箱(参照)」として扱われます
|
1 2 3 4 |
const array2D = [[1, 2], [1, 2]]; const set = new Set(array2D); console.log(set.size); // 2 (中身が同じでも重複とみなされない) |
二次元配列の重複を削除する方法
二次元配列で「中身が同じもの」を除外したい場合は、一度データを文字列化して比較するのが一般的です。
方法1:JSON.stringify を使う(簡単・確実)
一度 JSON.stringify() で文字列("[1, 2]")に変換して Set にかけ、最後に JSON.parse() で配列に戻します
|
1 2 3 4 5 |
const array2D = [[1, 2], [1, 2], [3, 4]]; const uniqueArray = [...new Set(array2D.map(JSON.stringify))].map(JSON.parse); console.log(uniqueArray); // [[1, 2], [3, 4]] |
方法2:filter と join を使う(数値や単純な文字列のみの場合)
中身が単純な数値などの場合、join() でカンマ区切りの文字列にして判別することも可能です
|
1 2 3 4 5 6 7 8 9 |
const array2D = [['car', 2], ['bike', 1], ['car', 2]]; const seen = new Set(); const unique = array2D.filter(item => { const key = item.join(','); // "car,2" という文字列を作る return seen.has(key) ? false : seen.add(key); }); console.log(unique); // [["car", 2], ["bike", 1]] |
まとめ
- そのまま使う: 同じ「インスタンス(変数)」なら重複排除されるが、中身が同じだけの別配列は排除されない。
- 解決策:
map(JSON.stringify)で一度文字列にしてからSetに入れる。
コメント