Skip to content

Commit

Permalink
Added nwise
Browse files Browse the repository at this point in the history
  • Loading branch information
basvankuijck authored and freak4pc committed Aug 15, 2020
1 parent 4a9b16c commit 309d427
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ All notable changes CombineExt will be documented in this file.
## Unreleased

* Add `Zip` for multiple Publishers (#6)
* Add `Nwise` for consecutive elements (#27)
8 changes: 8 additions & 0 deletions CombineExt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

/* Begin PBXBuildFile section */
AA9C3E7A249858840002A111 /* CombineSchedulers in Frameworks */ = {isa = PBXBuildFile; productRef = AA9C3E79249858840002A111 /* CombineSchedulers */; };
C387777C24E6BBE900FAD2D8 /* Nwise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C387777B24E6BBE900FAD2D8 /* Nwise.swift */; };
C387777F24E6BF8F00FAD2D8 /* NwiseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C387777D24E6BF6C00FAD2D8 /* NwiseTests.swift */; };
OBJ_100 /* ZipMany.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* ZipMany.swift */; };
OBJ_101 /* CurrentValueRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* CurrentValueRelay.swift */; };
OBJ_102 /* PassthroughRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* PassthroughRelay.swift */; };
Expand Down Expand Up @@ -92,6 +94,8 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
C387777B24E6BBE900FAD2D8 /* Nwise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Nwise.swift; sourceTree = "<group>"; };
C387777D24E6BF6C00FAD2D8 /* NwiseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NwiseTests.swift; sourceTree = "<group>"; };
"CombineExt::CombineExt::Product" /* CombineExt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CombineExt.framework; sourceTree = BUILT_PRODUCTS_DIR; };
"CombineExt::CombineExtTests::Product" /* CombineExtTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = CombineExtTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
OBJ_10 /* Sink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sink.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -202,6 +206,7 @@
OBJ_23 /* FlatMapLatest.swift */,
OBJ_24 /* MapMany.swift */,
OBJ_25 /* Materialize.swift */,
C387777B24E6BBE900FAD2D8 /* Nwise.swift */,
OBJ_26 /* Partition.swift */,
OBJ_27 /* PrefixDuration.swift */,
OBJ_28 /* RemoveAllDuplicates.swift */,
Expand Down Expand Up @@ -245,6 +250,7 @@
OBJ_48 /* FlatMapLatestTests.swift */,
OBJ_49 /* MapManyTests.swift */,
OBJ_50 /* MaterializeTests.swift */,
C387777D24E6BF6C00FAD2D8 /* NwiseTests.swift */,
OBJ_51 /* OptionalTests.swift */,
OBJ_52 /* PartitionTests.swift */,
OBJ_53 /* PassthroughRelayTests.swift */,
Expand Down Expand Up @@ -492,6 +498,7 @@
OBJ_125 /* CombineLatestManyTests.swift in Sources */,
OBJ_126 /* CreateTests.swift in Sources */,
OBJ_127 /* CurrentValueRelayTests.swift in Sources */,
C387777F24E6BF8F00FAD2D8 /* NwiseTests.swift in Sources */,
OBJ_128 /* DematerializeTests.swift in Sources */,
OBJ_129 /* FlatMapLatestTests.swift in Sources */,
OBJ_130 /* MapManyTests.swift in Sources */,
Expand All @@ -517,6 +524,7 @@
OBJ_79 /* DemandBuffer.swift in Sources */,
OBJ_80 /* Sink.swift in Sources */,
OBJ_81 /* Optional.swift in Sources */,
C387777C24E6BBE900FAD2D8 /* Nwise.swift in Sources */,
OBJ_82 /* Event.swift in Sources */,
OBJ_83 /* ObjectOwnership.swift in Sources */,
OBJ_84 /* Amb.swift in Sources */,
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ All operators, utilities and helpers respect Combine's publisher contract, inclu
* [share(replay:)](#sharereplay)
* [prefix(duration:tolerance:​on:options:)](#prefixduration)
* [toggle()](#toggle)
* [nwise(_:) and pairwise()](#nwise)

### Publishers
* [AnyPublisher.create](#AnypublisherCreate)
Expand Down Expand Up @@ -537,6 +538,58 @@ true
false
```

### nwise(_:) and pairwise()

#### nwise(_:)

Groups the elements of the source publisher into arrays of N consecutive elements.

```swift
let subject = PassthroughSubject<Int, Never>()

subscription = subject
.nwise(3)
.sink(receiveValue: { print($0) })

subject.send(1)
subject.send(2)
subject.send(3)
subject.send(4)
subject.send(5)
```

```none
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
```

#### pairwise

Groups the elements of the source publisher into tuples of the previous and current elements

```swift
let subject = PassthroughSubject<Int, Never>()

subscription = subject
.pairwise()
.sink(receiveValue: { print("\($0.0) -> \($0.1)") })

subject.send(1)
subject.send(2)
subject.send(3)
subject.send(4)
subject.send(5)
```

```none
1 -> 2
2 -> 3
3 -> 4
4 -> 5
```


## Publishers

This section outlines some of the custom Combine publishers CombineExt provides
Expand Down
44 changes: 44 additions & 0 deletions Sources/Operators/Nwise.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Nwise.swift
// CombineExt
//
// Created by Bas van Kuijck on 14/08/2020.
// Copyright © 2020 Combine Community. All rights reserved.
//

#if canImport(Combine)
import Combine

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public extension Publisher {
/// Groups the elements of the source publisher into arrays of N consecutive elements.
/// The resulting publisher:
/// - does not emit anything until the source publisher emits at least N elements;
/// - emits an array for every element after that;
/// - forwards any errors or completed events.
///
/// - parameter size: size of the groups, must be greater than 1
///
/// - returns: A type erased publisher that holds an array with the given size.
func nwise(_ size: Int) -> AnyPublisher<[Output], Failure> {
assert(size > 1, "n must be greater than 1")

return scan([]) { acc, item in Array((acc + [item]).suffix(size)) }
.filter { $0.count == size }
.eraseToAnyPublisher()
}

/// Groups the elements of the source publisher into tuples of the previous and current elements
/// The resulting publisher:
/// - does not emit anything until the source publisher emits at least 2 elements;
/// - emits a tuple for every element after that, consisting of the previous and the current item;
/// - forwards any error or completed events.
///
/// - returns: A type erased publisher that holds a tuple with 2 elements.
func pairwise() -> AnyPublisher<(Output, Output), Failure> {
return nwise(2)
.map { ($0[0], $0[1]) }
.eraseToAnyPublisher()
}
}
#endif
98 changes: 98 additions & 0 deletions Tests/NwiseTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// CurrentValueRelayTests.swift
// CombineExtTests
//
// Created by Bas van Kuijck on 14/08/2020.
// Copyright © 2020 Combine Community. All rights reserved.
//

#if !os(watchOS)
import XCTest
import Combine
import CombineExt

private struct PairwiseTuple<T: Equatable>: Equatable {
let element1: T
let element2: T

init(_ tuple: (T, T)) {
element1 = tuple.0
element2 = tuple.1
}

init(_ element1: T, _ element2: T) {
self.element1 = element1
self.element2 = element2
}

static func == (lhs: PairwiseTuple<T>, rhs: PairwiseTuple<T>) -> Bool {
return lhs.element1 == rhs.element1 && lhs.element2 == rhs.element2
}
}

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
class NwiseTests: XCTestCase {
private var subscriptions = Set<AnyCancellable>()

func testNwise() {
var expectedOutput: [[Int]] = []
var completion: Subscribers.Completion<Never>?

Publishers.Sequence(sequence: [1, 2, 3, 4, 5, 6])
.nwise(3)
.sink(
receiveCompletion: { completion = $0 },
receiveValue: { expectedOutput.append($0) }
).store(in: &subscriptions)

XCTAssertEqual(
expectedOutput,
[
[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]
]
)
XCTAssertEqual(completion, .finished)
}

func testNwiseNone() {
var completion: Subscribers.Completion<Never>?

Publishers.Sequence(sequence: [1, 2, 3])
.nwise(4)
.sink(
receiveCompletion: { completion = $0 },
receiveValue: { XCTAssert(false, "Should not receive a value, got \($0)") }
).store(in: &subscriptions)

XCTAssertEqual(completion, .finished)
}

func testPairwise() {
var expectedOutput: [PairwiseTuple<Int>] = []
var completion: Subscribers.Completion<Never>?

Publishers.Sequence(sequence: [1, 2, 3, 4, 5, 6])
.pairwise()
.sink(
receiveCompletion: { completion = $0 },
receiveValue: { expectedOutput.append(PairwiseTuple($0)) }
).store(in: &subscriptions)

XCTAssertEqual(
expectedOutput,
[
PairwiseTuple(1, 2),
PairwiseTuple(2, 3),
PairwiseTuple(3, 4),
PairwiseTuple(4, 5),
PairwiseTuple(5, 6),
]
)
XCTAssertEqual(completion, .finished)
}

}
#endif

0 comments on commit 309d427

Please sign in to comment.