Imagine I want to create a function that, given an array of numbers, computes the square, cube, and fourth power of each number in an asynchronous fashion and returns a flattened, asynchronous sequence of all these results.
So, for example, for the input array [2, 3, 4]
, it should return an AsyncSequence
instance yielding the elements [4, 8, 16, 9, 27, 81, 16, 64, 256]
.
Then let's say, instead of computing x^2, x^3, x^4
, I would like it to compute x, x^2, x^3, ..., x^k
where k
is sort of a random integer that can be different for every x and is not known beforehand (its value comes to be known only as the powers are being computed). How would I implement such a pattern?
CodePudding user response:
An AsyncStream
could do the job. E.g., given an array of integers, values
, the asynchronous sequence would be:
let stream = AsyncStream<Int> { continuation in
Task.detached {
for value in values {
var result = value
for _ in 1 ..< n {
result *= value
continuation.yield(result)
}
}
continuation.finish()
}
}
But this calculation of x², x³, ..., xⁿ for each element in the input array might not be a good candidate for an asynchronous sequence. Each subsequent value can be calculated nearly instantaneously (just multiplying the previously emitted value by x) and, as such, should probably just be a standard, synchronous sequence.
Generally, asynchronous sequences should be those that are sufficiently slow to justify moving it into the background or otherwise has results that are emitted asynchronously over time.
CodePudding user response:
Thanks a lot to Rob for providing the basic idea on how to implement something like this.
I wrote it in the following way:
func powers(of numbers: [Int]) -> AsyncStream<Int> {
return AsyncStream<Int> { continuation in
Task {
for number in numbers {
for await power in Powers(of: number) {
continuation.yield(power)
}
}
continuation.finish()
}
}
}
struct Powers: AsyncSequence {
init(of base: Int) {
self.base = base
}
func makeAsyncIterator() -> PowersIterator {
return PowersIterator(base: self.base)
}
let base: Int
typealias Element = Int
}
struct PowersIterator: AsyncIteratorProtocol {
mutating func next() async -> Int? {
if !self.shouldFinish() {
try? await Task.sleep(nanoseconds: 1_000_000_000)
defer {
self.exponent = 1
}
return power(self.base, self.exponent)
} else {
return nil
}
}
private func shouldFinish() -> Bool {
return Int.random(in: 1...10) == 1
}
private func power(_ base: Int, _ exponent: UInt) -> Int {
return (0..<exponent).reduce(1) { power, _ in power * base }
}
var exponent = UInt(1)
let base: Int
}
It can be invoked using this code:
Task {
let numbers = [1, 2, 3, 4, 5]
for await power in powers(of: numbers) {
print(power, terminator: " ")
}
}
Possible output:
1 1 1 2 4 8 16 32 64 3 9 27 4 16 64 256 5 25 125 625 3125 15625
The solution is a little more complex than it ought to be. But that's of course because I actually wanted to compute something that would really need to be computed asynchronously and has the same computational structure as this example. That is also the reason for why I created a separate async sequence for computing the powers.
If this helps anyone out, I'll be glad.