0

What is the most idiomatic way to process a value a given number of times when that value is derived from another publisher and the resultant value is used in the next iteration?

If the value didn't come from another publisher I would use reduce.

Since I own this code I can rewrite to not return a publisher but I would like to know how this situation should be handled using Combine.

import Combine
import Foundation

let iterations = 1000
let initialValue = 0
let intSubject = PassthroughSubject<Int, Never>()

// Iterate over a value a number of times passing the result to the next iteration.
// This seems a lot like reduce but the problem is the reduce closure returns a value not a publisher.

let x = intSubject
        .flatMap { number in
            // This is the important part.  The number from the previous iteration
            // is needed to create the next publisher.  In the real life scenario this
            // result object is more complex than an Int.  Integer addition is used
            // here as an example I am not looking for a way to solve integer addition.
            return number.addOne()
        }
        .handleEvents(receiveOutput: { (number: Int) in
            DispatchQueue.main.async {
                intSubject.send(number)
            }
            return
        })
        .handleEvents(receiveRequest: { _ in
            DispatchQueue.main.async {
                intSubject.send(initialValue)
            }
        })
        .prefix(iterations)
        .eraseToAnyPublisher()

let subscription = x.sink(receiveValue: { number in print("\(number)") })

extension Int {
    func addOne() -> AnyPublisher<Int, Never> {
        // Only returns a single value and completes
        [self].publisher.map { $0 + 1 }.eraseToAnyPublisher()
    }
}

UPDATE 1

I decided perhaps my contrived example was concealing some things so at the risk of complicating things here is my real scenario. I was able to make a small simplication by using CurrentValueSubject instead of PassthroughSubject which handles the initial value nicely.

I am still not convinced that using handleEvents is the best way to propagate updated values back for the next iteration. Additionally I don't want to use .sink as I want to pass the publisher on outside of this function for later subscription.

    public func evolve(generations: Int, offspringCount: Int, populationLimit: Int, randomNumberGenerator: RandomNumberSource) -> AnyPublisher<GenePool<Environment>, GenePoolError> {
        let genePoolSubject = CurrentValueSubject<GenePool<Environment>, Never>(self)

        return genePoolSubject
                .flatMap { genePool -> AnyPublisher<GenePool<Environment>, GenePoolError> in
                    genePool.evolve(
                        offspringCount: offspringCount,
                        populationLimit: populationLimit,
                        randomNumberGenerator: randomNumberGenerator
                    )
                }
                .handleEvents(receiveOutput: { (pool: GenePool<Environment>) in
                    DispatchQueue.main.async {
                        genePoolSubject.send(pool)
                    }
                })
                .prefix(generations)
                .eraseToAnyPublisher()
    }

    private func evolve(offspringCount: Int, populationLimit: Int, randomNumberGenerator: RandomNumberSource) -> AnyPublisher<GenePool<Environment>, GenePoolError> { ... }
nacross
  • 2,013
  • 2
  • 25
  • 37
  • how you will determine in above scenario when to stop sending values? I can see you keep sending values once you receive output. – Tushar Sharma Apr 11 '22 at 09:04
  • In the above scenario I will stop after the defined number of iterations using `.prefix(iterations)`. Some further background - I am writing a genetic algorithm just for fun and am using this as a test case to improve my understanding of Combine which is admittedly limited. The process involves running an "evolve" operation a number of times on a Population type this itself performs several operations I wanted to run concurrently before returning an updated Population instance. I felt this extra information might cloud the essence of the problem so I supplied the above integer example. – nacross Apr 11 '22 at 09:34
  • from my understanding you want to send previous output value again, and get new output (like prev + 2) and keep doing until some condition is verified ? – Tushar Sharma Apr 11 '22 at 09:59
  • Yes it sounds right to me. – nacross Apr 11 '22 at 10:06
  • which publisher do you want to pass outside? The one provided by flatMap? – Tushar Sharma Apr 11 '22 at 11:14
  • In the initial example it would be the property `x` in the subsequent example from update 1 it is the return value of the function. So this function should return a publisher that will perform the input modification a set number of times using the value from the previous iteration in the next. – nacross Apr 11 '22 at 11:21
  • Sorry I’m a bit confused of what you looking for, deleted my existing answer as it wasn’t the correct solution. – Tushar Sharma Apr 11 '22 at 11:25

0 Answers0