JavaScript/TypeScript 配列の反復処理メソッドまとめ

皆さん、こんにちは。LP開発グループのn-ozawanです。
熊が人里に降りてくるというニュースが連日報道されています。環境省が公表している熊による被害件数では、昨年の令和6年では人身被害件数が82件に対して、今年は現時点で176件と倍になっています。紅葉シーズンで登山される方は熊対策をして十分に気を付けて楽しんでください。

本題です。
プログラムで配列にデータを格納することは良くあります。そしてその配列をfor文等のループ処理により検索や加工を行うことも良くあります。その為、昔から配列の反復処理を行うメソッドが存在します。今回はJavaScript/TypeScriptの反復処理メソッドをまとめます。

反復処理メソッド

forEach()

最も初歩的な反復処理メソッドです。配列の先頭から末尾まで順次処理を行います。引数には各ループで処理したい関数を指定します。for文と比べてcontinue(次のループ)break(中断)が使えません。

// Expected output:
// Value: 1, Index: 0, Array: [1,2,3,4,5]
// Value: 2, Index: 1, Array: [1,2,3,4,5]
// Value: 3, Index: 2, Array: [1,2,3,4,5]
// Value: 4, Index: 3, Array: [1,2,3,4,5]
// Value: 5, Index: 4, Array: [1,2,3,4,5]
function sample_forEach() {
  const array = [1, 2, 3, 4, 5];
  array.forEach((value, index, arr) => {
    console.log(`Value: ${value}, Index: ${index}, Array: [${arr}]`);
  });
}

find() / findIndex() / findLast() / findLastIndex()

配列から特定の条件に合致する値1つを探索します。find()は先頭から探索して条件に合致したらその値を返却します。findIndex()は値ではなく合致したインデックスを返却します。findLast()は末尾から探索して値を返却します。findLastIndex()はインデックスを返却します。どれも該当する要素が見つかり次第、ループ処理を終了して結果を返却します。

// Expected output:
// find(): 3
// findIndex(): 2
// findLast(): 5
// findLastIndex(): 4
function sample_find() {
  const array = [1, 2, 3, 4, 5];

  // find(): 最初に条件を満たす値を返します。
  const found = array.find((value) => value > 2);
  console.log(`find(): ${found}`); // 3

  // findIndex(): 最初に条件を満たす値のインデックスを返します。
  const foundIndex = array.findIndex((value) => value > 2);
  console.log(`findIndex(): ${foundIndex}`); // 2

  // findLast(): 最後に条件を満たす値を返します。
  const foundLast = array.findLast((value) => value > 2);
  console.log(`findLast(): ${foundLast}`); // 5

  // findLastIndex(): 最後に条件を満たす値のインデックスを返します。
  const foundLastIndex = array.findLastIndex((value) => value > 2);
  console.log(`findLastIndex(): ${foundLastIndex}`); // 4
}

filter()

配列を特定の条件に合致する要素だけを抽出します。

// Expected output:
// array: [1,2,3,4,5]
// filtered: [2,4]
function sample_filter() {
  const array = [1, 2, 3, 4, 5];
  const filtered = array.filter((value) => value % 2 === 0);
  console.log(`array: [${array}]`); // [1,2,3,4,5]
  console.log(`filtered: [${filtered}]`); // [2,4]
}

filter()は元の配列に影響を与えず、新しい配列を生成します。もし非常に大きい配列の場合、メモリを圧迫することになりますので注意が必要です。例えば複数の条件に合致する要素のみを抽出したい場合、filter()を連続して呼び出すのではなく、1回のfilter()で処理した方が良いでしょう。

function sample_filter_NG() {
  const array = [1, 2, 3, 4, 5]; // 仮にこの配列が非常に大きい場合

  // condA: 2以上の値、condB: 偶数
  const condA = (value: number) => value >= 4;
  const condB = (value: number) => value % 2 === 0;

  // 非効率的な方法: 2回filterを呼び出す
  const result1 = array.filter(condA).filter(condB);

  // 効率的な方法: 1回のfilterで両方の条件をチェック
  const result2 = array.filter(value => condA(value) && condB(value));
}

map() / flatMap()

配列要素の値もしくは型を変更します。とある型の配列を、別の型の配列に変換するのに良く使います。filter()と同様に元の配列に影響を与えず、新しい配列を生成しますので、非常に大きい配列への使用には注意が必要です。flatMap()は配列をフラット化します。フラット化とは、多次元配列を1次元配列に変換することです。

// Expected output:
// array: [1,2,3]
// mapped: [["No.1","Value:1"],["No.2","Value:2"],["No.3","Value:3"]]
// flatMapped: ["No.1","Value:1","No.2","Value:2","No.3","Value:3"]
function sample_map() {
  const array = [1, 2, 3];
  console.log(`array: [${array}]`); // [1,2,3]
  
  const map = (value: number) => [`No.${value}`, `Value:${value}`]

  // map(): 各要素に対してマッピングを行います。
  const mapped = array.map(map);
  console.log(`mapped: ${JSON.stringify(mapped)}`); // [["No.1","Value:1"],["No.2","Value:2"],["No.3","Value:3"]]

  // flatMap(): 各要素に対してマッピングし、結果の配列をフラット化します。
  const flatMapped = array.flatMap(map);
  console.log(`flatMapped: ${JSON.stringify(flatMapped)}`); // ["No.1","Value:1","No.2","Value:2","No.3","Value:3"]
}

every() / some()

every()は配列の全ての要素が特定の条件に合致すればtrue、合致しなければfalseを返却します。some()は配列の要素の内、どれが1つでも特定の条件に合致すればtrue、合致しなければfalseを返却します。

// Expected output:
// every(): false
// some(): true
function sample_every_some() {
  const array = [1, 2, 3, 4, 5];

  // every(): 全ての要素が条件を満たす場合にtrueを返します。
  const isAllEven = array.every(value => value % 2 === 0);
  console.log(`every(): ${isAllEven}`); // false

  // some(): 少なくとも1つの要素が条件を満たす場合にtrueを返します。
  const hasEven = array.some(value => value % 2 === 0);
  console.log(`some(): ${hasEven}`); // true
}

reduce() / reduceRight()

配列要素を集約して1つの情報に変換します。集計処理などで使います。reduce()は先頭から、reduceRigth()は末尾から処理します。

// Expected output:
// reduce(): -1-2-3-4-5
// reduceRight(): -5-4-3-2-1
function sample_reduce() {
  const array = [1, 2, 3, 4, 5];

  // reduce(): 配列の左から右へ集約処理を行います。
  const sum = array.reduce((acc, value) => acc + '-' + value, '');
  console.log(`reduce(): ${sum}`); // -1-2-3-4-5

  // reduceRight(): 配列の右から左へ集約処理を行います。
  const concatRight = array.reduceRight((acc, value) => acc + '-' + value, '');
  console.log(`reduceRight(): ${concatRight}`); // -5-4-3-2-1
}

おわりに

反復処理メソッドは可読性が上がりますが、意識しないと無駄なループ処理が生じてしまい、想定以上のメモリ消費とCPU負荷を与えてしまいます。

以下は配列の特定の要素に対して2倍する処理です。sample01()sample02()は同じ結果になりますが、後者の方が効率的です。しかし、sample02()は可読性を犠牲にしていますので、どちらがいいのかは簡単には決めれません。SQLなどで取得するデータ量を抑制したり、ページング処理などで多くのデータを扱わないなど、予め大きな配列を生成しないように配慮することで安心して反復処理メソッドを使うことができます。

function sample01() {
  const array = [1, 2, 3, 4, 5];  // 仮にこの配列が非常に大きい場合
  // filterとmapを連続で呼び出す方法 → 2回ループしているため非効率
  const result = array.filter((value) => value % 2 === 0).map((value) => value * 2);
  console.log(`result: [${result}]`); // [4,8]
}

function sample02() {
  const array = [1, 2, 3, 4, 5];  // 仮にこの配列が非常に大きい場合

  // 単一のループでfilterとmapの両方を実行する方法 → 1回のループで済むため効率的
  let result = [];
  for (const value of array) {
    if (value % 2 === 0) {
      result.push(value * 2);
    }
  }

  console.log(`result: [${result}]`); // [4,8]
}

ではまた。

Recommendおすすめブログ