Javascriptのreduce()メソッド:配列のインデックス、初期値、オブジェクト、合計 非同期、ブレーク
JavaScriptのArray.prototype.reduce()
メソッドは、最もよく使われる配列メソッドの1つです、
これはJavaScriptの関数型プログラミングの代表例です。
プログラミングを習い始めると、手続き型プログラミングとオブジェクト指向プログラミングを学ぶことになる、
reduce()
メソッドを直感的に理解するのは必ずしも簡単ではありません。
そこで今回は、関数型プログラミングから様々な実例まで、reduce()
メソッドを理解するために必要なことを紹介します。
1. JavaScript関数型プログラミングの概要
関数型プログラミング は、数学的な意味での関数に基づいてプログラムの動作を設計するテクニックを指すプログラミングパラダイムです。
関数型プログラミングでは、関数に同じ値を入れさえすれば、常に同じ結果が得られるはずです。 この原則に従った関数は純粋関数と呼ばれ、その振る舞いの中でいかなる状態値も変更してはならない。 もしそうであれば、それは副作用と呼ばれ、関数型プログラミングは副作用を最小限に抑えることを目的としています。
また、ステートとデータの不変性を重視し、データ構造の値を変更する代わりに
データ構造の値を変更する代わりに、新しいデータ構造を作成するようにプログラムを設計します。
つまり、配列 [1, 2, 3]
を [1, 2, 4]
に変更したい場合は、新しい配列 [1, 2, 4]
を作成します。
このように設計されたプログラムは、データを同時にアクセスする並行プログラミングのようなものにとって強力な利点があります。
最後に、JavaScriptは関数を第一級市民として扱います、 これは関数型プログラミングに最適な言語です。 その結果、多くのJS開発者は保守性と信頼性の高いコードを書くために関数型プログラミングを使っています。
関数型として定義される関数は
const add = (a, b) => a + b;
const result = add(1, 2); // Output is always 3
1.1. reduce()メソッドが関数型プログラミングにとって重要な理由
reduce()
メソッドはJavaScriptの関数型プログラミングの原則を守っています。
これは特に、配列を1つの値に縮小するときや、配列の要素に基づいて結果を累積するときに当てはまります。
また、副作用もありません。
その結果、よりクリーンで保守性の高いコードを書くために不可欠なメソッドとなります。
2. reduce()メソッドを理解する:初期化、インデックス、アキュムレータ
reduce()
メソッドは配列を走査するメソッドで、配列の各要素に対して与えられた関数(reducer)を実行し、1つの出力値を生成します。
基本的な構文は
array.reduce(reducerFunction, initialValue);
// reducerFunction
array.reduce(((accumulator, currentValue, currentIndex, array) => returnValue), initialValue);
initialValue
は reducerFunction
の戻り値に加算される初期値である。
数値の和を得るには 0
を、最終的な文字列を得るには ""
を使用する。
reducerFunction
は4つのパラメータを受け取るコールバック関数である。
- accumulator (
accumulator
) - 直前のインデックスまでの全ての要素に関数を適用した戻り値を累積する。 - currentValue (
currentValue
) - 関数が現在適用されている項目の値。 - index (
currentIndex
) - 現在の要素の配列インデックス(オプション)。 - array (
array
) - 配列自身への参照(オプション)。
各パラメータがどのような値を持つのか、簡単な例を見てみよう。
const numbers = [ 1, 2, 3 ]
numbers.reduce((accumulator, currentValue, currentIndex, array) => {
console.log(`accumulator: ${accumulator}, currentValue: ${currentValue}, currentIndex: ${currentIndex}, array: ${array} `)
return accumulator + currentValue
}, 0);
// Output:
// accumulator: 0, currentValue: 1, currentIndex: 0, array: 1,2,3
// accumulator: 1, currentValue: 2, currentIndex: 1, array: 1,2,3
// accumulator: 3, currentValue: 3, currentIndex: 2, array: 1,2,3
// 6
インデックス 0
を持つリデューサ関数が最初に呼び出されたとき、ア キュムレータの初期値は 0
です。
reducer関数は現在の値 1
をアキュムレータの値に加算して返します。
この処理を繰り返し、最終的に reduce()
関数がアキュムレータ値 6
を返します。
言い換えると、上記の例の動作を解凍すると、次のようになります。
const numbers = [ 1, 2, 3 ]
const reducerFunction = element => element
const returnValue = reducerFunction(numbers[0]) + reducerFunction(numbers[1]) + reducerFunction(numbers[2])
console.log(returnValue)
// Output: 6
2.1. breakとcontinueキーワードに注意
reduce()
関数を含むほとんどの配列メソッドは、探索操作を操作する break,
continue
キーワードをサポートしていません。
continue
キーワードを使用するなど、現在の要素に何もしたくない場合は、条件文を使用して蓄積された値をそのまま返すことができます。
以下の例では、蓄積せずに 3
を通過させている。
const numbers = [ 1, 2, 3 ]
const reducerFunction = (accumulator, element) => {
console.log(`누산기: ${accumulator}, 현재값: ${element}`)
if (element === 3) {
return accumulator
}
return accumulator + element
}
numbers.reduce(reducerFunction, 0)
// Output: 3
3. オブジェクトを操作するための使い方
reduce()
メソッドは、オブジェクトのプロパティにアクセスして様々な操作を行うのにも便利です。
オブジェクトのプロパティに直接アクセスすることもできますし、Object.entries()
のようなヘルパーメソッドを使うこともできます。
ひとつずつ見ていきましょう。
const people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 25 },
{ name: 'David', age: 30 }
];
const groupedByAge = people.reduce((accumulator, currentValue) => {
const ageGroup = currentValue.age;
accumulator[ageGroup] = accumulator[ageGroup] || [];
accumulator[ageGroup].push(currentValue);
return accumulator;
}, {});
console.log(groupedByAge);
// Output:
// {
// '25': [
// { name: 'Alice', age: 25 },
// { name: 'Charlie', age: 25 }
// ],
// '30': [
// { name: 'Bob', age: 30 },
// { name: 'David', age: 30 }
// ]
// }
上記の例では、オブジェクトの age
プロパティにアクセスし、オブジェクトを年齢に基づいて2つのグループに分類するタスクを実行した。
const grades = {
math: 90,
science: 80,
history: 85,
english: 95
};
const totalScore = Object.entries(grades).reduce((accumulator, [subject, score]) => {
return accumulator + score;
}, 0);
console.log(totalScore); // Output: 350
1つのオブジェクトの複数の値を操作する必要がある場合、Objext.entries()
メソッドを使用することができます。
このメソッドは、オブジェクトのプロパティとプロパティ値を1つの配列として操作します。
これにより、オブジェクトに対しても reduce()
関数を使うことができ、以下のようになります、
subject, score]
のような代入の構造化を解除することで、より簡潔なコードを書くことができます。
4. 重複除去のような実際の応用例
JSのfilter()関数 - 3.filter()による重複排除では、コード例として重複排除のテクニックも紹介しました。
reduce()
関数を使えば、配列から重複要素を取り除くコードも簡単に書けます。
まずはコードを見てみよう。
const array = [1, 2, 3, 2, 4, 3, 5, 1];
const uniqueArray = array.reduce((accumulator, element) => {
if (!accumulator.includes(element)) {
accumulator.push(element);
}
return accumulator;
}, []);
console.log(uniqueArray);
// Output: [1, 2, 3, 4, 5]
上記のコードでは、初期値を空の配列 []
に設定し、現在の要素が accumulator{js}
配列に存在しない場合に新しい要素を追加する。
Array.includes()
メソッドを使用してインクルードを決定する。
JS filter()関数 - 4.includes()によるコールバック関数の定義で使用した
String.includes()
メソッドと比較してみてください。
reduce
関数が返す結果配列は、重複しない要素のみを取り出していることがわかります。
5. 非同期コールバック関数の使用
配列メソッドで非同期コールバック関数を使う方法は、JS map()メソッド - 6.非同期コールバック:非同期コールバック関数で説明しました。
map()
メソッドは、Promise.all()
メソッドとともに、非同期+並行して動作するコードを書くことができました。
しかし、reducer
メソッドは非同期コールバック関数では使用できません。
これを回避するには、async await
キーワードと Promise
オブジェクトを使用して、非同期に動作する独自の reduce 関数を定義する必要があります。
まずはサンプルコードを見てみましょう。
const array = [1, 2, 3, 4, 5];
const asyncReduce = async (array, callback, initialValue) => {
let accumulator = initialValue;
for (const element of array) {
accumulator = await callback(accumulator, element);
}
return accumulator;
};
const sumAsync = async (a, b) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b);
}, 1000);
});
};
const calculateSum = async () => {
const sum = await asyncReduce(array, sumAsync, 0);
console.log(sum);
};
calculateSum();
// Output: 15
この例の asyncReduce()
関数は、for of
構文で配列を走査し、非同期コールバック関数を呼び出し、すべての処理が完了するまで await
する。
任意の非同期コールバック関数 sumAsync()
は1秒待ってから加算結果を返す。
最後に、calculateSum()
関数が asyncReduce()
関数を実際に実行するメイン関数として機能する。
このコードを実行すると、一定時間後にすべての数値の和 15
が得られる。
6. 結論
JavaScriptの reduce()
メソッドは、関数型プログラミングのための強力で多機能なツールです。
このメソッドを使うと、不変性や単純性といった原則を維持したまま、配列に対して演算を行うことができます。
他の配列メソッドと組み合わせて賢く使い、直感的で読みやすいコードを書くようにしましょう。
