[Combine] reduce を使って sum, min, max を求める

Swift

Combine の便利なオペレータ reduce をあらためて説明してみます。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterery beta 5
  • Xcode 13 beta5
  • iOS 15 beta

Combine のオペレータ

Combine のオペレータには、さまざまな種類があります。そのうちの1つに reduce があります。

reduce がすこし違うのは、stream に流れてきた要素の他に 前回処理時の値を処理できる点です。

[Int] の Publisher を作る

以下のコードで、1,2,...,10 という Int が流れてくる Publisher を作ることができます。


(1...10).publisher

確認するために map を使って値を2倍にしてみると以下のようになります。


let result = (1...10).publisher.map{ $0 * 2 }
print(result.sequence)
// print-out
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

(1...10).publisher という Publisher から流れてきた値を使って、Array が作られていることがわかります。

reduce

reduce で記述できる処理は、他のオペレータと少し異なります。

違う点は、reduce での処理は、引数を2つ受け取ることです。

1つめの引数は、前回の(reduce からの)返り値、2つめの引数は stream からきた値です。

初回動作時には 前回の返り値 は存在しません。そのため、初期値を与えることが必要となります。

reduce は、以下のような使い方になります。


somePublisher.reduce(初期値, {前回の返り値、今回の値 in { 今回の返り値を返す }) 

reduce を使うと、stream 中で 前回の値を覚えておくことができるということです。

この特徴を使って、sum や min/max を計算できるようになります。

reduce を使って、合計を計算する

覚えておくことができる値に そこまでの合計値を保存しておくことで、全体の合計値を計算することができます。

以下では、accum がそれまでの合計値を保持しています。初期値としては 0 を使います。


let result = (1...10).publisher.reduce(0, {accum, next in accum + next })
print(try! result.result.get())
// print-out
55

初期値 0 に最初にきた値 1 を加算して、その結果を返す。次に2が来たときに、前回の結果(1) に 2を加算し、その結果を返す。次に3が来たときに・・・・・と10まで繰り返しています。

計算式にすると下のような計算をおこなっていることになります。

\( ((((((((((0 + 1) + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9) + 10) \)

reduce を使って、最小値を計算する

覚えておくことができる値に そこまでの最小値を保存することで、全体での最小値計算にも使えます。

以下では、min はそれまでの最小値を保持しています。初期値としては、Int で保持できる最大値 Int.max を使います。


let result = (1...10).publisher.reduce(Int.max, {min, next in min < next ? min : next })
print(try! result.result.get())
// print-out
1

初期値の Int.max と最初にきた値 1 を比較し 小さい方(1) を返す。次に2が来たときに、前回の結果(1) と比較し 小さい方(1) を返す。次に3が来たときに・・・・と10まで繰り返しています。

reduce を使って、最大値

最小値の時と同様の仕組みを使い、最大値を保持することで、全体の最大値計算にも使うことができます。

以下では、max はそれまでの最小値を保持しています。初期値としては、Int で保持できる最小値 Int.min を使います。


let result = (1...10).publisher.reduce(Int.min, {max, next in next < max ? max : next })
print(try! result.result.get())
// print-out
10

初期値の Int.min と最初にきた値 1 を比較し 大きい方(1) を返す。次に2が来たときに、前回の結果(1) と比較し 大きい方(2) を返す。次に3が来たときに・・・・と10まで繰り返しています。

Publisher からの値が Int でなくとも reduce は使える

+-*/ や< >を使って簡単に記述できるので Int を例題に使っていますが、class や struct の特定のプロパティを使って、最大値(相当)や合計値(相当)を計算させることももちろんできます。

reduce で計算できないもの

Publisher からの値を順次処理するだけなので、処理対象の値全体についての情報が必要となるものは計算できません。

例えば、”平均”は、処理対象に値がいくつあるか知らないと計算できないため、reduce では計算できません。

まとめ:reduce で sum,min,max を求める

reduce で sum,min,max を求める
  • reduce では 前回の処理値を使うことができる
  • reduce の前回処理値に初期値を与えることができる
  • sum,min,max はreduce の前回処理値をつかうことで計算可能
  • average は、reduce では計算できない

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です