【JavaScript】reduce()の使い方と10の使用例
更新日:2024/02/27
JavaScriptのreduce()メソッドは配列の要素を集計するとき、欠かせないメソッドです。
様々な場面で使用できるので、使い慣れるとプログラムコードの効率が上がります。
reduce()とは
reduce()は配列の要素を順番に処理する関数です。
同じような処理を行う関数にforEach()があります。
それぞれ、次のような使用目的があります。
forEach():配列の個々の要素を個別に処理するメソッド
reduce():配列の全ての要素を使って一つの結果を求めるメソッド
reduce()は、次のような仕組みを持っています。
- (1)コールバック関数の処理結果を次の要素の処理に渡す
- (2)最後の要素で行った処理を関数の結果として返す
この仕組みを上手く使うことで、一つの結果を得ることができます。
reduce()の使い方
まずは、reduce()の使い方について紹介していきます。
簡単な例
まずは簡単な例を紹介します。
これだけでreduce()を理解できると思います。
reduce()で配列の総和を求める
const array = [3,5,7];
const sum = array.reduce(
// 第一引数:コールバック関数
( result // ← 初期値・前回のreturn値
, e // ← 配列の値
)=>{
return result + e;
}
// 第二引数:初期値
,0
);
console.log( sum ); // sum は 15
コメントで分かりにくくなってますね。
コールバック関数部分は、returnや{}を省略して次のように短縮できます。
こちらは、コメントを入れてないので分りやすいと思います。
const sum = array.reduce( ( result , e )=>result + e, 0 );
とてもスッキリとしましたね。
コールバック関数の一つ目の引数は一回目に初期値を受け取ります。
2回目以降は前回のコールバック関数が返した値を受け取ります。
二つ目の引数は、配列の値です。
上記コードのコールバック関数は、次のような値を受け取っています。
1回目:( 0 :初期値 , 3 :配列[0]の値 ) => return 0 + 3
2回目:( 3 :1回目の結果 , 5 :配列[1]の値 ) => return 3 + 5
3回目:( 8 :2回目の結果 , 7 :配列[2]の値 ) => return 8 + 7
const sum = 15
初期値は省略可能(非推奨)
reduce()の第二引数は、初期値を指定します。
ただし、次のように省略できます。
const sum = array.reduce( ( result , e )=>result + e );
省略すると一回目のコールバック関数が受け取る引数が、次のように変化します。
※要素数が2つ以上のとき | ||||
第一引数 | 第二引数 | |||
---|---|---|---|---|
初期値あり | 初期値 | 配列[0]要素 | ||
初期値無し | 配列[0]要素 | 配列[1]要素 |
上記コードの処理の流れは、次のようになります。
1回目:( 3 :配列[0]の値 , 5 :配列[1]の値 ) => return 3 + 5
2回目:( 8 :1回目の結果 , 7 :配列[2]の値 ) => return 8 + 7
const sum = 15
初期値ありと比較して、コールバック関数の呼び出し回数が1回減りました。
こちらの方が効率が良いですが、大きな問題があるのでおススメできません。
配列の要素数が1つまたは空のときは、関数の戻り値として次の値が返ります。
※要素数1以下のときの戻り値 | ||||
要素数 | 戻り値 | |||
---|---|---|---|---|
初期値あり | 1 | 初期値 | ||
0(空配列) | ||||
初期値なし | 1 | 配列[0]要素 | ||
0(空配列) | エラー TypeError: Reduce of empty array with no initial value |
初期値なしで配列に要素が無い場合、エラーが発生します。
そのため要素数を確認する必要があります。
エラー対応
const result = array.length === 0 ? 0 : array.reduce( );
コード量はあまり増えませんが、初期値を指定したほうが簡潔ですね。
この点からも、reduce()の第二引数で初期値を指定することを推奨します。
コールバック関数の引数
コールバック関数は全部で4つの引数を受け取ることができます。
reduceのコールバック関数構文
コールバック関数( previousValue , currentValue , currentIndex , array )
- previousValue : 初期値または前回のコールバック関数の戻り値
- currentValue : 配列要素の値
- currentIndex : 配列要素のインデックス
- array : 処理中の配列
外部変数の値を操作するforEach()との置き換え
forEach()で何らかの結果を求めるために外部変数を定義するケースがあります。
reduce()に置き換えると、外部変数の定義を排除できる可能性があります。
外部変数が一つの場合
次のコードは、配列中の数値データの数をforEach()でカウントしています。
const array = [1,"a",2,"b",3];
let count = 0;
array.forEach( e=>{ if(typeof e ==="number") ++count; } );
console.log( count ); // 3
外部変数が嫌ということではなくて、letを使いたくないです。
どうにかしたいですね。
次のようにreduce()に置き換えると解決です。
const count = array.reduce(
(result,e)=>typeof e ==="number" ? ++result : result
,0 );
console.log( count ); // 3
結果を受け取る変数を const で定義できるのが嬉しいですね。
外部変数が複数の場合
複数の外部変数を扱うこともできます。
まずは、forEach()バージョン。
数値データのカウントと、数値データの抜き出しを行っています。
const array = [1,"a",2,"b",3];
let count = 0;
const number = [];
array.forEach( e=>{
if(typeof e ==="number"){
++count;
number.push(e);
}
} );
console.log( count , number); // 3 , [1, 2, 3]
これを、reduce()に置き換えます。
ポイントは初期値にオブジェクトを指定する点です。
オブジェクトバージョン
const {count,number} = array.reduce( (result,e)=>{
if( typeof e ==="number" ){
result.count ++;
result.number.push(e);
}
return result; // ← 忘れずに!
},{count:0 , number:[]}
);
console.log( count ,number );
重要なのは、コールバック関数が必ず初期値と同じ形式のデータを返すこと。
通常は受け取った引数の値を変更して、その引数を返します。
初期値は配列でも大丈夫です。
次のように配列で書き換えることができます。
配列バージョン
const [count,number] = array.reduce( (result,e)=>{
if( typeof e ==="number" ){
result[0] ++;
result[1].push(e);
}
return result; // ← 忘れずに!
},[0 , []]
);
console.log( count ,number );
reduce()を中断する
reduce()には途中で処理を終了させる機能がありません。
そこで、初期値内にフラグを用意して処理を飛ばします。
const {count,error} = array.reduce( (result,e)=>{
if( result.error ) return result;
if( typeof e !=="number" ){
result.error = true;
}else{
result.count ++;
}
return result; // ← 忘れずに!
},{count:0 , error:false}
);
if( error ) console.log( "処理を中断しました" );
要素が数値以外のとき、errorプロパティにtrueをセットしています。
以降はコールバック関数の最初の行でretrunされるので、疑似的に処理がスキップされたことになります。
通常はこれで十分です。
しかし膨大な量のデータを処理する場合は、本来の意味で中断したほうがいいケースがあります。
このようなケースでは、例外をスローして処理を止めます。
const numberCount = ((array)=>{
try{
const count = array.reduce( (result,e)=>{
if( typeof e !=="number" ){
throw {count:result};
}
return result + e;
},0);
return {count:count , error:false};
}catch(e){
if( typeof e === "object" && "count" in e )
return {count:e.count, error:true};
throw e;
}
});
const {count,error} = numberCount([1,2,3]);
if( error ) console.log( "処理を中断しました" );
try{} の中で定義した変数は、その外側でアクセスできません。
そのため、reduce()の戻り値をtry{}外部でlet宣言した変数にセットする必要があります。
それを避けるために、関数化してあります。
reduce()の事例
reduce()を使用した事例を、いくつか紹介します。
なお型チェック等はおこなっていないので、必要に応じて追加してください。
配列の合計を求める
数値の合計は、reduce()の使用例で最も多く使用例として挙げられます。
配列要素の合計を求める
const array = [1,2,3];
const sum = array.reduce( (result,e) => result + e , 0 );
可変長引数を受け付ける合計関数
もう少し発展させて、可変長引数を受け付ける合計関数を作成してみます。
レスト構文を使用します。
可変長引数を受け付ける合計関数
const summary = (...values) => values.reduce(
(result,e) => result + e , 0 );
const value1 = 1, value2 = 2 , value3 = 3 , value4 = 4;
console.log( summary( value1 , value2 , value3 ) ); // 6
console.log( summary( value1 , value2 , value3 , value4 ) ); // 10
関数の引数定義でレスト構文を用いているので、引数valuesは配列に変換されています。
そのため、そのままreduce()を使用できます。
可変長引数を受け付ける合計関数(多次元配列対応)
さらに発展させて、引数に配列および多次元配列が指定されるケースを考慮してみます。
可変長引数を受け付ける合計関数(多次元配列対応)
const summary = (...values) => {
const summary2 = (values) => values.reduce(
(result,e) => result + (
Array.isArray(e) ? summary2(e) : e
) , 0 );
return summary2(values);
}
要素が配列のときは再帰呼び出しするのですが、summary()を呼び出すと次のように多次元配列が生成されます。
そしてまた、再帰呼び出しされるので無限ループです。
それを回避するため、レスト構文を使用しないバージョンの関数を作成して呼び出しています。
多次元配列の複製
多次元配列を新規の配列にコピーします。
多次元配列の複製
const arrayClone = array => array.reduce(
(resultArray,e)=>resultArray.push( Array.isArray( e )
? arrayClone2(e) : e ) ? resultArray : resultArray
),[]);
const array = [ 1 , [ 2 , 3 ] ];
const clonedArray = arrayClone( array );
初期値で空の配列を用意して順番にpush()しています。
このとき要素が配列なら、再帰呼び出ししています。
コールバック関数は初期値で作成した配列をリターンさせたいので、無理やり3項演算子を使用しています。
偶数値または奇数値を得る
配列要素から偶数値または奇数値を抜き出し、新規配列にセットして返します。
偶数値または奇数値を得る
const oddOrEvenValues = ( array , isOdd ) => {
const remainder = isOdd ? 1 : 0;
return array.reduce(
( resultArray , e ) => {
if( e % 2 === remainder ) resultArray.push( e );
return resultArray;
},[]);
}
// 奇数値を取得する
const oddValues = array => oddOrEvenValues( array , true );
// 偶数値を取得する
const evenValues = array => oddOrEvenValues( array , false );
const values = [1,2,3,4,5,6,7,8,9,10];
console.log( oddValues( values ) ); // [1,3,5,67,9]
console.log( evenValues( values ) ); // [2,4,6,8,10]
種類別に配列に振り分ける
配列の値を種類ごとに新規配列に振り分けます。
次のコードは、偶数(even)、奇数(odd)、非整数(notInteger)、数値以外(other)の4つに振り分けています。
const array = [1,"a",2,"b",3,"c",4,"d",1.4,1.6];
const {odd,even,notInteger,other} = array.reduce( (result,e)=>{
if( typeof e === "number" ){
if( !Number.isInteger( e ) ) result.notInteger.push(e);
else if( e % 2 === 1 ) result.odd.push( e );
else result.even.push( e );
}else result.other.push( e );
return result;
} , { odd:[] , even:[] , notInteger:[] , other:[] } );
console.log( odd ); // [1, 3]
console.log( even ); // [2, 4]
console.log( notInteger ); // [1.4, 1.6]
console.log( other ); // ['a', 'b', 'c', 'd']
二つの配列の各要素の演算結果を得る
二つの配列の同じインデックス要素に何らかの演算を行い、結果を新規配列にセットします。
この条件だとmap()を使用すべき(※1)なので、同時に他の処理もおこないます。
次のコードは、積と積の総合計を求めます。
const value1 = [1,2,3,4];
const value2 = [10,20,30,40];
const {result,total} = value1.reduce(
(resultObj , e , index)=>{
const value = e * value2[index];
resultObj.result.push( value );
resultObj.total += value;
return resultObj;
},{ result:[ ] , total:0 });
console.log( result ); // [10, 40, 90, 160]
console.log( total ); // 300
コールバック関数の3つ目の引数は処理中のインデックスです。
これを使って、二つ目の配列から値を取得しています。
const result = value1.map( (e,index)=>e*value2[index] );
DOM要素のスタイルを一括で得る
DOM要素のスタイルを一括で取得します。
DOM要素のスタイルを一括で得る
cconst stylesName = ["width","height","color","background-color"];
const domElement = document.getElementById("id");
const getStyles = (domElement,styleNames) => styleNames.reduce(
(result,e)=>{
result.resultObj[e] = result.allStyles[e];
return result;
}, { // 初期値
allStyles:window.getComputedStyle(domElement),
resultObj:{}
}
).resultObj;
console.log( getStyles(domElement,stylesName) );
// {
// background-color: "rgba(0, 0, 0, 0)"
// color: "rgb(0, 0, 0)"
// height: "24px"
// width: "1227px"
// }
window.getComputedStyle()は要素の全スタイルをオブジェクトで取得するメソッドです。
この関数で取得した値はreduce()内のみで使用するので、初期値に記述しています。
reduce()の結果にもこのオブジェクトが含まれますが、必要なデータ(resultObjプロパティ)のみを抜き出しています。
文字列配列を一定の長さまで連結する
文字列配列の連結をおこないますが、一定以上の長さを超える文は切り捨てます。
const result = textArray.reduce(
( resultObj , e )=>{
if( resultObj.end ) return resultObj;
const length = e.length;
if( resultObj.length + length > MAX_LENGTH ){
e = e.substr(0,MAX_LENGTH - resultObj.length);
resultObj.end = true;
}
resultObj.text.push(e);
resultObj.length += length;
return resultObj;
},{ text : [] , length : 0 ,end : false}
).text;
console.log( result.join("") ); // abcdefghij
実のところjoin()を実行した後にsubstr()で切り取った方が簡単だったりします。
textArray.join("").substr(0,10); // abcdefghij
そのため今回のコードは、内部で文字列を連結せずに配列のまま返しています。
これなら、意味があるはずです…
オブジェクトのメソッドを任意の順番で実行する
オブジェクトのメソッドを任意の順番で実行します。
その際、前回実行したメソッドの戻り値を引数として渡します。
そして最後のメソッドの戻り値を最終結果として受け取ります。
const obj = {
func1:param=>param+"やあ",
func2:param=>param+"今日は",
func3:param=>param+"明日は",
func4:param=>param+"いい天気",
func5:param=>param+"大雨",
func6:param=>param+"だね",
}
const pattern1 = ["func1","func2","func4","func6"];
const pattern2 = ["func1","func3","func5","func6"];
const executeFunc = (obj,pattern)=>pattern.reduce(
( result , funcName )=>obj[funcName](result)
, "");
console.log( executeFunc( obj , pattern1 ) ); // やあ今日はいい天気だね
console.log( executeFunc( obj , pattern2 ) ); // やあ明日は大雨だね
更新日:2024/02/27
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。