Swift 组合:使用滑动 window 功能进行收集
Swift Combine: Collect with Sliding window like functionality
所以我有这个联合发布者说发出整数 - 1,2,3,4,5,6,7,8,9,10,11 ....
这些整数虽然并不总是存在。它们是即时生成并发送给发布者的。
我知道 collect 运算符,当它被称为 .collect(5) 时给我 - [1,2,3,4,5], [6,7,8,9,10], ...
我正在寻找的有点像缓冲区和收集的组合 - [1,2,3,4,5], [2,3,4,5,6], [3,4,5,6,7] ...
.
有没有不用写自定义运算符的方法?如果自定义运算符是可行的方法,您能否为此提供一些指导,我之前没有编写过自定义运算符。我希望我所要求的是从例子中清楚的。
谢谢
编辑
这可行,但我不确定这是否是最佳解决方案 -
publisher.zip(
publisher.dropFirst(),
publisher.dropFirst(2))
您可以使用 scan
运算符来实现此效果。 Scan
使您能够累积一个值——在本例中是一个以前值的数组——然后发出它。您只需要删除最初的 N-1 个较小的数组。
为方便起见,您可以创建自定义运算符 sliding(window:)
:
extension Publisher {
func sliding(window: Int) -> AnyPublisher<[Output], Failure> {
if window < 1 { return Empty().eraseToAnyPublisher() }
return self
.scan([], { arr, value in
if arr.count < window {
return arr + [value]
} else {
return arr.dropFirst() + [value]
}
})
.dropFirst(window - 1)
.eraseToAnyPublisher()
}
}
用法是:
[1,2,3,4,5,6,7,8,9,10].publisher
.sliding(window: 5)
.sink { print([=11=]) }
输出为:
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]
[5, 6, 7, 8, 9]
[6, 7, 8, 9, 10]
如果您确定您的发布者总是至少发出 N 个元素(N 是 window 大小),那么 New Dev 提供的解决方案就可以正常工作。
但是如果你想涵盖所有情况,那么事情会变得更加复杂,因为没有简单的方法可以滑动 window,同时如果第一个元素不足以填充 [= =20=].
一个可能的解决方案涉及更长更复杂的运算符管道,如下所示:
extension Publisher {
func sliding(window count: Int) -> AnyPublisher<[Output], Failure> {
self
// 1. collect batches of the given size
.collect(count)
// 2. we need to retain the last two batches, to be able to detect the case
// where we have only one batch, that could be smaller than the window size
.scan(([], [])) { ([=10=].1, ) }
// 3. now that the publisher emitted batches, we either publish the last batch
// if we have only one, or we generate an array of batches by appending elements
// from the last batch into the second last one
.map { secondLast, last in secondLast.isEmpty ? [last] : last.indices.map { secondLast + last[...[=10=]] } }
// 4. now that we have an array of batches, we need to flatten them, and
// for this we use MergeMany over an array of Just() publishers created from the batches
.flatMap { Publishers.MergeMany([=10=].map(Just.init)).setFailureType(to: Failure.self) }
// 5. now we only keep the N elements from the batch
// we could've done this at step 3, however that would've complicate that step even more
.map { [=10=].suffix(count) }
// 6. and, done :)
.eraseToAnyPublisher()
}
}
用法
_ = (1...4).publisher
.sliding(window: 5)
.sink(receiveValue: { print([=11=]) })
// [1, 2, 3, 4]
_ = (1...10).publisher
.sliding(window: 5)
.sink(receiveValue: { print([=11=]) })
// [1, 2, 3, 4, 5]
// [2, 3, 4, 5, 6]
// [3, 4, 5, 6, 7]
// [4, 5, 6, 7, 8]
// [5, 6, 7, 8, 9]
// [6, 7, 8, 9, 10]
我相信 OP 的原始解决方案更加优雅、惯用且安全。唯一的缺点是我们不能将大小作为参数传递,但如果我们考虑一下,在大多数情况下我们需要明确指定大小,所以这完全是一回事。
extension Publisher {
func window2() -> AnyPublisher<(Output, Output), Failure> {
zip(dropFirst()).eraseToAnyPublisher()
}
func window3() -> AnyPublisher<(Output, Output, Output), Failure> {
zip(dropFirst(), dropFirst(2)).eraseToAnyPublisher()
}
}
所以我有这个联合发布者说发出整数 - 1,2,3,4,5,6,7,8,9,10,11 ....
这些整数虽然并不总是存在。它们是即时生成并发送给发布者的。
我知道 collect 运算符,当它被称为 .collect(5) 时给我 - [1,2,3,4,5], [6,7,8,9,10], ...
我正在寻找的有点像缓冲区和收集的组合 - [1,2,3,4,5], [2,3,4,5,6], [3,4,5,6,7] ...
.
有没有不用写自定义运算符的方法?如果自定义运算符是可行的方法,您能否为此提供一些指导,我之前没有编写过自定义运算符。我希望我所要求的是从例子中清楚的。
谢谢
编辑
这可行,但我不确定这是否是最佳解决方案 -
publisher.zip(
publisher.dropFirst(),
publisher.dropFirst(2))
您可以使用 scan
运算符来实现此效果。 Scan
使您能够累积一个值——在本例中是一个以前值的数组——然后发出它。您只需要删除最初的 N-1 个较小的数组。
为方便起见,您可以创建自定义运算符 sliding(window:)
:
extension Publisher {
func sliding(window: Int) -> AnyPublisher<[Output], Failure> {
if window < 1 { return Empty().eraseToAnyPublisher() }
return self
.scan([], { arr, value in
if arr.count < window {
return arr + [value]
} else {
return arr.dropFirst() + [value]
}
})
.dropFirst(window - 1)
.eraseToAnyPublisher()
}
}
用法是:
[1,2,3,4,5,6,7,8,9,10].publisher
.sliding(window: 5)
.sink { print([=11=]) }
输出为:
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]
[5, 6, 7, 8, 9]
[6, 7, 8, 9, 10]
如果您确定您的发布者总是至少发出 N 个元素(N 是 window 大小),那么 New Dev 提供的解决方案就可以正常工作。
但是如果你想涵盖所有情况,那么事情会变得更加复杂,因为没有简单的方法可以滑动 window,同时如果第一个元素不足以填充 [= =20=].
一个可能的解决方案涉及更长更复杂的运算符管道,如下所示:
extension Publisher {
func sliding(window count: Int) -> AnyPublisher<[Output], Failure> {
self
// 1. collect batches of the given size
.collect(count)
// 2. we need to retain the last two batches, to be able to detect the case
// where we have only one batch, that could be smaller than the window size
.scan(([], [])) { ([=10=].1, ) }
// 3. now that the publisher emitted batches, we either publish the last batch
// if we have only one, or we generate an array of batches by appending elements
// from the last batch into the second last one
.map { secondLast, last in secondLast.isEmpty ? [last] : last.indices.map { secondLast + last[...[=10=]] } }
// 4. now that we have an array of batches, we need to flatten them, and
// for this we use MergeMany over an array of Just() publishers created from the batches
.flatMap { Publishers.MergeMany([=10=].map(Just.init)).setFailureType(to: Failure.self) }
// 5. now we only keep the N elements from the batch
// we could've done this at step 3, however that would've complicate that step even more
.map { [=10=].suffix(count) }
// 6. and, done :)
.eraseToAnyPublisher()
}
}
用法
_ = (1...4).publisher
.sliding(window: 5)
.sink(receiveValue: { print([=11=]) })
// [1, 2, 3, 4]
_ = (1...10).publisher
.sliding(window: 5)
.sink(receiveValue: { print([=11=]) })
// [1, 2, 3, 4, 5]
// [2, 3, 4, 5, 6]
// [3, 4, 5, 6, 7]
// [4, 5, 6, 7, 8]
// [5, 6, 7, 8, 9]
// [6, 7, 8, 9, 10]
我相信 OP 的原始解决方案更加优雅、惯用且安全。唯一的缺点是我们不能将大小作为参数传递,但如果我们考虑一下,在大多数情况下我们需要明确指定大小,所以这完全是一回事。
extension Publisher {
func window2() -> AnyPublisher<(Output, Output), Failure> {
zip(dropFirst()).eraseToAnyPublisher()
}
func window3() -> AnyPublisher<(Output, Output, Output), Failure> {
zip(dropFirst(), dropFirst(2)).eraseToAnyPublisher()
}
}