【JavaScript】Arrayのreduce関数を自由に使いこなす

配列の reduce 関数は、配列から一つの何か(オブジェクト、数値、文字列)を作りたい時に使います。

いつものように具体例から。

let array = [1, 2, 3, 4, 5]
let sum = array.reduce(function (sum, num) {
  return sum + num
}, 0)
console.log(sum)
// 15

配列内のすべての数の和を出しました。 reduce(関数, 初期値) という形で使います。 reduce に渡す関数を理解するのは少しやっかいです。

と、その前に同じことをアロー関数で書き直しておきます。

let sum = array.reduce((sum, num) => sum + num, 0)

reduce に渡した関数 (sum, num) => sum + num について。 第一引数 sum は「前までに評価された値」で、第二引数 num は「次に渡される配列の値」です。わかりにくい。

reduce 関数は、他の map 関数や filter 関数と同じように、配列の要素を先頭から一つずつ取り出して、何らかの処理をします。 reduce 関数がやりたいことは、「配列の要素それぞれに同じ関数を実行して最終的に一つの値に還元する(reduceする)」ということです。たとえば、配列の要素それぞれに「足す」という関数を実行して総和を求めるといったふうに。

先ほどの sum を for 文で書くと以下のようになります。

let sum = 0
let plus = (sum, num) => sum + num
for (let num of array) {
  sum = plus(sum, num)
}
console.log(sum)
// 15

reduce_.keyByを自前実装

lodash に入っているいくつかの関数は、 reduce を使えば自前でパパっと実装できます。

_.keyBy は、オブジェクトの配列から、あるプロパティをキーとしたオブジェクトを作ります。

たとえば、次のようなオブジェクトの配列があったとして、

let books = [
  {
    id: 'aaa',
    title: 'Book1'
  },
  {
    id: 'bbb',
    title: 'Book2'
  },
  {
    id: 'ccc',
    title: 'Book3'
  }
]

これを、 id をキーとしたオブジェクトにしたい。

let bookMap = {
  aaa: {
    id: 'aaa',
    title: 'Book1'
  },
  bbb: {
    id: 'bbb',
    title: 'Book2'
  },
  ccc: {
    id: 'ccc',
    title: 'Book3'
  }
}

この変換は、 _.keyBy を使うとこう書けます。

const _ = require('lodash')
let bookMap = _.keyBy(books, 'id')

それで、同じことを reduce を使うとこうなります。

let bookMap = books.reduce(
  (map, book) => Object.assign(map, { [book.id]: book }),
  {}
)

何をしているかというと、まず初期値に空のオブジェクト {} をセットする。次に book を一つずつ取り出して、オブジェクトに { [book.id]: book } というキー・バリューを追加しているわけです。

慣れればあまり考えずに書けるようになります。が、正直、知っていないと可読性が低いというか解読に時間がかかるため、そういう意味では lodash を使ったほうがすっきりはします。

reduce_.groupByを自前実装

次は、_.groupByを実装してみます。(第二引数に文字列を渡すやつだけ。)

let array = ['one', 'two', 'three']
let group = _.groupBy(array, 'length')
console.log(group)
// { '3': ['one', 'two'], '5': ['three'] }

配列内の各オブジェクトのプロパティで分類して、プロパティの値をキーとしたオブジェクトにしています。これを reduce で書くと、次のようになります。

// length で groupBy する
let group = array.reduce(
  (group, str) => Object.assign(group, {
    [str.length]: (group[str.length] || []).concat(str)
  }),
  {}
)

groupBy をそのまま実装したければ、次のような感じでしょうか。

// property は文字列
function groupBy (array, property) {
  return array.reduce(
    (group, item) => Object.assign(group, {
      [item[property]]: (group[item[property]] || []).concat(item)
    }),
    {}
  )
}

reduce_.uniqを自前実装

_.uniq も実装できます。重複のない配列に変換するメソッドです。

let array = [1, 2, 2, 3, 2, 1]
_.uniq(array)
// [1, 2, 3]

reduce を使うと…

function uniq (array) {
  let uniqSet = array.reduce(
    (set, item) => set.add(item),
    new Set()
  )
  return Array.from(uniqSet)
}

いったん Set にして重複をなくし、それから配列に直しています。

こんな感じで、 reduce を使って lodash のメソッドを自前実装してみると、 reduce の使い方が手に馴染んできますね。