🐥

JavaScript reduce関数の第二引数 initialValueに配列を指定することで実現する集計配列の実装

2025/01/31に公開2


reduceを使う方法

ワンライナーで if-elseの条件分けを JavaScript でできないかといつも思っていました。

普通に考えたらそこまで難しくないが、あまりこのような書き方を見なかったのでメモ。

たとえば、配列の値を条件分けしてそれぞれの値を集計したいタスクを考える。

一番シンプルに考えるのであれば、事前に変数を宣言しておき for文を回して
その中で変数にインクリメントするか、配列にpushするという選択肢が考えられる。

ただ、少しスマートではないと思ったので、reduce関数 の第二引数 initialValue
配列を指定することでそのような要件を達成したいと考えた。
今回は以下のように0も含むような配列を考慮する

let input = [-1, 3, 4, -2, -3, 6, -7, 0, 0]

実現した実装は以下の通りである。

動作としてはreduce関数の第一引数内のアロー関数のなかで、第一引数には配列を、第二引数には numを指定する。第1引数には「蓄積された値」、第2引数には「現在の要素」となる。初期値として [0,0] を渡している。numの条件次第で渡した値に前置インクリメントを加え加算していく。

reduce([callback],初期値) 関数におけるcallbackは前の結果の影響を受けるのでそのまま加算されていくという仕組みである。

let result1 = input.reduce(([positive, negative], num) => num > 0 ? 
                            [++positive, negative] : 
                            [positive, ++negative], 
                            [0, 0])
console.log(result1) //この場合は集計値である[ 3, 6 ]を値として得られる。

また、コードの可読性は落ちるが、Javascriptの三項演算子は以下のようにすることで
if,else-if,elseのようにも記述可能である。
これを応用すれば、三つの条件に関して値を集計できそれぞれを値として保持できる。

let variable = (condition) ? 
               (true block) : 
               ((condition2) ? (true block2) : (else block2))
let result2 = input.reduce(([positive, negative, zero], num) => num > 0 ? 
              ([++positive, negative, zero]) :
              ((num < 0) ? 
              ([positive, ++negative, zero]) : [positive, negative, ++zero]), [0, 0, 0])
console.log(result2) //この場合は集計値である[ 3, 4, 2 ]を値として得られる。

この書き方でいいなぁと思ったのは、事前に変数を定義しなくても状態を記録できる点にあると思う。
条件にもよるが、宣言しなくてもよい変数を定義することでコード量は増える可能性もあるので、今回の書き方は役に立てば幸いです。

補足(groupByを使う方法)

コメントで、 groupByを使う実装を教えていただいたのでその方法も追加します。
Object.groupBy の中で positivenegative のキーを持ったオブジェクトをグループ化することができ、values 関数で 値のみを取得し、それぞれの配列の長さを計算できれば要件を満たすことができる。

const input = [-1, 3, 4, -2, -3, 6, -7, 0, 0]
const result = Object.values(Object.groupBy(input, (i) => (i > 0 ? "positive" : "negative")))
                    .map((array) => array.length);
console.log(result)

Discussion

junerjuner

Object.groupBy を使うのも手ですね。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy

const input = [-1, 3, 4, -2, -3, 6, -7, 0, 0]
const result = Object.values(Object.groupBy(input, (i) =>  i > 0 ? 0 : 1))
    .map((array) => array.length)
console.log(result);
tac-tac-gotac-tac-go

たしかに groupBy を使うとよりシンプルに記述できそうですね。
補足コメントに記載させていただきます。
コメントありがとうございます😄