Falling Max Swift Combine Publisher - swift

I'm working on a publisher for Swift/Combine
Given a stream of inputs, I want to record the max value.
If the next number is lower, take one from the last recorded max value and emit that.
Input: [1,2,3,4,5,2,3,3,1]
Output: [1,2,3,4,5,4,3,3,2]
I can do this easily with the following code, however, I really don't like the instance variable
var lastMaxInstanceValue: Float = 0
publisher
.map { newValue
if newValue > lastMaxInstanceValue {
lastMaxInstanceValue = newValue
} else {
lastMaxInstanceValue = max(0, lastMaxInstanceValue - 1)
}
}
.assign(to: \.percentage, on: self)
.store(in: &cancellables)
So I wrote a publisher/subscriber here which encapsulates the map part above:
https://github.com/nthState/FallingMaxPublisher
With my publisher, the code turns into:
publisher
.fallingMax()
.assign(to: \.percentage, on: self)
.store(in: &cancellables)
My question is, is my GitHub publisher necessary? Can the value I want be calculated without having the extra variable?

You can use scan operator to achieve this. Scan stores an accumulated value computed from each upstream value and the currently accumulated value. You do however, need to give it an initial value; based on your example I used 0:
publisher
.scan(0, { max, value in
value >= max ? value : max - 1
})
You can implement a fallingMax operator for SignedIntegers - as in your github - like so:
extension Publisher where Output: SignedInteger {
func fallingMax(initial: Output = 0,
fadeDownAmount: Output = 1
) -> AnyPublisher<Output, Failure> {
self.scan(initial, { max, value in
value >= max ? value : max - fadeDownAmount
})
.eraseToAnyPublisher()
}
}
As per #Rob's suggestion, if you don't want to supply an initial value, which would instead use the first value as the initial output, you can use an Optional (notice the .compactMap to bring it back to non-optional):
extension Publisher where Output: SignedInteger {
func fallingMax(initial: Output? = .none,
fadeDownAmount: Output = 1
) -> AnyPublisher<Output, Failure> {
self.scan(initial, { max, value in
max.map { value >= $0 ? value : $0 - fadeDownAmount } ?? value
})
.compactMap { $0 }
.eraseToAnyPublisher()
}
}

Related

Intersection of ranges (of floating point ranges)

I am asking a user for four ranges of floating point numbers. I want to check that there is no overlap between them.
If the ranges were integer ranges it seems that I could either create sets or use Swift Range (or NSRange) and check for intersections.
Is there a way to figure this out if the ranges where then upper and lower bounds are floating point values?
Would I just have to check that each lower and upper bound of each range is not between the lower/upper bound of each of the other ranges? Is there a better way?
Thanks
You can use the Range type to represent a range of floating point values. You can then use the contains method to check if a value is in a range. You can also use the overlaps method to check if two ranges overlap. Here is an example:
let range1 = 1.0..<2.0
let range2 = 1.5..<3.0
let range3 = 2.0..<4.0
range1.contains(1.5) // true
range1.overlaps(range2) // true
range1.overlaps(range3) // false
You can also use the ~= operator to check if a value is in a range. This is the same operator that is used in switch statements.
let value = 1.5
switch value {
case range1:
if range1.contains(value) {
print("value is in range1")
}
case range2:
if range2.contains(value) {
print("value is in range2")
}
case range3:
if range3.contains(value) {
print("value is in range3")
}
default:
print("value is not in any range")
}
You can use the sorted method to sort an array of ranges. Here is an example:
let ranges = [range1, range2, range3]
let sortedRanges = ranges.sorted { $0.lowerBound < $1.lowerBound }
You can then iterate over the sorted ranges and check if the upper bound of the previous range is greater than the lower bound of the current range.
for (index, range) in sortedRanges.enumerated() {
if index > 0 {
let previousRange = sortedRanges[index - 1]
if previousRange.upperBound > range.lowerBound {
print("ranges overlap")
}
}
}
You can also use the contains/overlaps method to check if a range contains another range.
for (index, range) in sortedRanges.enumerated() {
if index > 0 {
let previousRange = sortedRanges[index - 1]
if range.contains(previousRange) {
print("ranges overlap")
}
if range.overlaps(previousRange) {
print("ranges overlap")
}
}
}
There is no way to express it as a property yet; it has to be a function.
import Algorithms
extension Sequence {
func containsAnOverlap<Bound>() -> Bool
where Element == ClosedRange<Bound> {
sorted(by: \.lowerBound).adjacentPairs().contains { $0.overlaps($1) }
}
}
public extension Sequence {
/// Sorted by a common `Comparable` value.
func sorted(
by comparable: (Element) throws -> some Comparable
) rethrows -> [Element] {
try sorted(by: comparable, <)
}
/// Sorted by a common `Comparable` value, and sorting closure.
func sorted<Comparable: Swift.Comparable>(
by comparable: (Element) throws -> Comparable,
_ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
) rethrows -> [Element] {
try sorted {
try areInIncreasingOrder(comparable($0), comparable($1))
}
}
}
[2...3.0, 0...1.0].containsAnOverlap() // false
[1...3.0, 0...2.0].containsAnOverlap() // true

How to capture the first value in a publisher one way and the rest another way

I have a publisher where I want to handle receiving the first value one way, and receiving the rest another way. Currently I have the following:
let bar = foo
.first()
.sink { value in
print("first value is \(value)")
}
let baz = foo
.dropFirst()
.sink { value in
print("one of the rest is \(value)")
}
foo.send(1)
foo.send(2)
foo.send(3)
foo.send(4)
foo.send(5)
foo.send(6)
This works. But it forces me to have two subscriptions, one for the first value, and another for the rest. Is there a clever way of combining the two into one subscription (without external flag management). Something like:
let bar = foo
.first()
.sink { value in
print("first value is \(value)")
}
.dropFirst()
.sink {
print("the rest is \(value)")
}
I'm aware the above doesn't make sense (and is probably uglier than the original solution) since a publisher is singular flow that doesn't branch (beyond value, completion, error), but that's due to my lack of creativity, I'm hoping someone here has something that I might be overlooking.
One typical approach is to use something like .scan to map into a tuple that tells the rest of the pipeline whether this is the first one or not.
In this simple example, the "rest of the pipeline" is simply .sink, but you can see that it does know which one is first.
class ViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
let subject = PassthroughSubject<Int, Never>()
override func viewDidLoad() {
super.viewDidLoad()
var firstPassed = false
subject
.scan((0, false)) {prev, this in
defer { firstPassed = true }
return (this, firstPassed)
}
.sink {
if !$0.1 {
print($0.0, "is the first one")
} else {
print($0.0, "is not the first one")
}
}
.store(in: &cancellables)
subject.send(1)
subject.send(2)
subject.send(3)
subject.send(4)
subject.send(5)
}
}
Result:
1 is the first one
2 is not the first one
3 is not the first one
4 is not the first one
5 is not the first one
var cancellables = Set<AnyCancellable>()
let foo = PassthroughSubject<Int, Never>()
let first = foo
.first()
let restOfFirst = foo
.dropFirst()
Publishers.CombineLatest(first, restOfFirst) // <= here
.sink {
print("first value: \($0), rest Of other values\($1)")
}
.store(in: &cancellables)
Resualt:
foo.send(1)
foo.send(2)
foo.send(3)
foo.send(4)
foo.send(5)
foo.send(6)
first value: 1, rest Of other values: 2
first value: 1, rest Of other values: 3
first value: 1, rest Of other values: 4
first value: 1, rest Of other values: 5
first value: 1, rest Of other values: 6

Why does `Publishers.Map` consume upstream values eagerly?

Suppose I have a custom subscriber that requests one value on subscription and then an additional value three seconds after it receives the previous value:
class MySubscriber: Subscriber {
typealias Input = Int
typealias Failure = Never
private var subscription: Subscription?
func receive(subscription: Subscription) {
print("Subscribed")
self.subscription = subscription
subscription.request(.max(1))
}
func receive(_ input: Int) -> Subscribers.Demand {
print("Value: \(input)")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
self.subscription?.request(.max(1))
}
return .none
}
func receive(completion: Subscribers.Completion<Never>) {
print("Complete")
subscription = nil
}
}
If I use this to subscribe to an infinite range publisher, back pressure is handled gracefully, with the publisher waiting 3 seconds each time until it receives the next demand to send a value:
(1...).publisher.subscribe(MySubscriber())
// Prints values infinitely with ~3 seconds between each:
//
// Subscribed
// Value: 1
// Value: 2
// Value: 3
// ...
But if I add a map operator then MySubscriber never even receives a subscription; map appears to have synchronously requested Demand.Unlimited upon receiving its subscription and the app infinitely spins as map tries to exhaust the infinite range:
(1...).publisher
.map { value in
print("Map: \(value)")
return value * 2
}
.subscribe(MySubscriber())
// The `map` transform is executed infinitely with no delay:
//
// Map: 1
// Map: 2
// Map: 3
// ...
My question is, why does map behave this way? I would have expected map to just pass its downstream demand to the upstream. Since map is supposed to be for transformation rather than side effects, I don't understand what the use case is for its current behavior.
EDIT
I implemented a version of map to show how I think it ought to work:
extension Publishers {
struct MapLazily<Upstream: Publisher, Output>: Publisher {
typealias Failure = Upstream.Failure
let upstream: Upstream
let transform: (Upstream.Output) -> Output
init(upstream: Upstream, transform: #escaping (Upstream.Output) -> Output) {
self.upstream = upstream
self.transform = transform
}
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Upstream.Failure {
let mapSubscriber = Subscribers.LazyMapSubscriber(downstream: subscriber, transform: transform)
upstream.receive(subscriber: mapSubscriber)
}
}
}
extension Subscribers {
class LazyMapSubscriber<Input, DownstreamSubscriber: Subscriber>: Subscriber {
let downstream: DownstreamSubscriber
let transform: (Input) -> DownstreamSubscriber.Input
init(downstream: DownstreamSubscriber, transform: #escaping (Input) -> DownstreamSubscriber.Input) {
self.downstream = downstream
self.transform = transform
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
downstream.receive(transform(input))
}
func receive(completion: Subscribers.Completion<DownstreamSubscriber.Failure>) {
downstream.receive(completion: completion)
}
}
}
extension Publisher {
func mapLazily<Transformed>(transform: #escaping (Output) -> Transformed) -> AnyPublisher<Transformed, Failure> {
Publishers.MapLazily(upstream: self, transform: transform).eraseToAnyPublisher()
}
}
Using this operator, MySubscriber receives the subscription immediately and the mapLazily transform is only executed when there is demand:
(1...).publisher
.mapLazily { value in
print("Map: \(value)")
return value * 2
}
.subscribe(MySubscriber())
// Only transforms the values when they are demanded by the downstream subscriber every 3 seconds:
//
// Subscribed
// Map: 1
// Value: 2
// Map: 2
// Value: 4
// Map: 3
// Value: 6
// Map: 4
// Value: 8
My guess is that the particular overload of map defined for Publishers.Sequence is using some kind of shortcut to enhance performance. This breaks for infinite sequences, but even for finite sequences eagerly exhausting the sequence regardless of the downstream demand messes with my intuition. In my view, the following code:
(1...3).publisher
.map { value in
print("Map: \(value)")
return value * 2
}
.subscribe(MySubscriber())
ought to print:
Subscribed
Map: 1
Value: 2
Map: 2
Value: 4
Map: 3
Value: 6
Complete
but instead prints:
Map: 1
Map: 2
Map: 3
Subscribed
Value: 2
Value: 4
Value: 6
Complete
Here's a simpler test that doesn't involve any custom subscribers:
(1...).publisher
//.map { $0 }
.flatMap(maxPublishers: .max(1)) {
(i:Int) -> AnyPublisher<Int,Never> in
Just<Int>(i)
.delay(for: 3, scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
.sink { print($0) }
.store(in: &storage)
It works as expected, but then if you uncomment the .map you get nothing, because the .map operator is accumulating the infinite upstream values without publishing anything.
On the basis of your hypothesis that map is somehow optimizing for a preceding sequence publisher, I tried this workaround:
(1...).publisher.eraseToAnyPublisher()
.map { $0 }
// ...
And sure enough, it fixed the problem! By hiding the sequence publisher from the map operator, we prevent the optimization.

"PassthroughSubject" seems to be thread-unsafe, is this a bug or limitation?

"PassthroughSubject" seems to be thread-unsafe. Please see the code below, I'm sending 100 values concurrently to a subscriber which only request .max(5). Subscriber should only get 5 values I think, but it actually got more. Is this a bug or limitation?
// Xcode11 beta2
var count = 0
let q = DispatchQueue(label: UUID().uuidString)
let g = DispatchGroup()
let subject = PassthroughSubject<Int, Never>()
let subscriber = AnySubscriber<Int, Never>(receiveSubscription: { (s) in
s.request(.max(5))
}, receiveValue: { v in
q.sync {
count += 1
}
return .none
}, receiveCompletion: { c in
})
subject.subscribe(subscriber)
for i in 0..<100 {
DispatchQueue.global().async(group: g) {
subject.send(i)
}
}
g.wait()
print("receive", count) // expected 5, but got more(7, 9...)
I believe the prefix operator can help:
/// Republishes elements up to the specified maximum count.
func prefix(Int) -> Publishers.Output<PassthroughSubject<Output, Failure>>
The max operator is returning the largest value at completion (and it's possible you're triggering completion more than once):
/// Publishes the maximum value received from the upstream publisher, after it finishes.
/// Available when Output conforms to Comparable.
func max() -> Publishers.Comparison<PassthroughSubject<Output, Failure>>

High order functions assignation

I'm learning about High Order functions in Swift (like .map .filter .reduce...) and generics types.
Here is my function :
func max<T: Comparable>(_ array: [T]) -> T {
var max = 0 as! T
for value in array {
if value > max { max = value }
}
return max
}
How can I replace my for loop with high order function to get the same result ?
Im looking to do something like this (or better) :
max = array.map { $0 > max ? $0 : max }
Reduce!
return array.reduce(nil)
{
(max: T?, current: T) -> T? in
guard let max = max else { return current }
return max > current ? max : current
}
That will return an optional but that is probably sensible given you might pass in an empty array.
Of course there is also this
https://developer.apple.com/documentation/swift/array/1688806-max
The implication of tyour question is that this is a learning exercise. So here is a generalisation of the solution that makes use of higher order functions. Note that the Swft Strandard Library already contains a function that does this.
extension Array
{
func pickOne(choose: (Element, Element) -> Element) -> Element?
{
return self.reduce(nil)
{
(bestSoFar: Element?, current: Element) -> Element? in
guard let bestSoFar = bestSoFar else { return current }
return choose(bestSoFar, current)
}
}
}
So the functionality of max is now defined like this:
array.pickOne { $0 > $1 ? $0 : $1 }
and min would be
array.pickOne { $0 < $1 ? $0 : $1 }
First note that your approach for the "initial value" and the forced
cast
var max = 0 as! T
has two problems:
It will crash for arrays not containing integers, e.g. max(["a", "b"]).
Even for integer arrays, it is wrong if all array elements are
negative, e.g. max([-2, -3]) should be -2 and not zero.
So you better choose the first array element as initial value instead
of the "forced zero".
That leads to the next question: What if the array
is empty? There are two valid approaches: You can require that the
function is called with a non-empty array (and document that
precondition):
/// Compute the maximal element in an array.
///
/// - Returns: The maximal element.
///
/// - Note: The array must not be empty.
func max<T: Comparable>(_ array: [T]) -> T {
precondition(!array.isEmpty, "`max` called with empty array")
var max = array[0]
for value in array {
if value > max { max = value }
}
return max
}
Or (as also suggested in the other answers) make the return value
optional:
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
guard var max = array.first else { return nil }
for value in array {
if value > max { max = value }
}
return max
}
Both approaches can be implemented with reduce().
The first one would be
/// Compute the maximal element in an array.
///
/// - Returns: The maximal element.
///
/// - Note: The array must not be empty.
func max<T: Comparable>(_ array: [T]) -> T {
precondition(!array.isEmpty, "`max` called with empty array")
return array.reduce(array[0]) { $0 > $1 ? $0 : $1 }
}
and the second one
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
guard let first = array.first else { return nil }
return array.reduce(first) { $0 > $1 ? $0 : $1 }
}
This can be further shortened using the flatMap() method
of Optional:
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
return array.first.flatMap { array.reduce($0) { $0 > $1 ? $0 : $1 } }
}
Finally you can use the existing
func max<T : Comparable>(_ x: T, _ y: T) -> T
function instead of a literal closure in all of the above examples, e.g.
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
return array.first.flatMap { array.reduce($0, max) }
}
For Ints you would want to use reduce for this like so:
// Reduce with initial value the first value of the array if available,
// or 0 otherwise
let max = array.reduce(array.first ?? 0) { (max, newValue) -> T in
return newValue > max ? newValue : max
}
UPDATE
You want JeremyP's answer for a proper handling of all Comparable!