Skip to content

Commit

Permalink
Fix CollectionProperty filter and sort operators.
Browse files Browse the repository at this point in the history
  • Loading branch information
srdanrasic committed Jun 5, 2016
1 parent 4ba4dd3 commit c6a672a
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 40 deletions.
4 changes: 2 additions & 2 deletions ReactiveKit.podspec
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Pod::Spec.new do |s|
s.name = "ReactiveKit"
s.version = "2.0.3"
s.version = "2.0.4"
s.summary = "A Swift Reactive Programming Framework"
s.description = "ReactiveKit is a collection of Swift frameworks for reactive and functional reactive programming."
s.homepage = "https://github.com/ReactiveKit/ReactiveKit"
s.license = 'MIT'
s.author = { "Srdan Rasic" => "[email protected]" }
s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v2.0.3" }
s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v2.0.4" }

s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.9'
Expand Down
12 changes: 8 additions & 4 deletions ReactiveKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
168103451D046388007D1DD4 /* CollectionPropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168103431D046388007D1DD4 /* CollectionPropertyTests.swift */; };
168103461D046388007D1DD4 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168103441D046388007D1DD4 /* Helpers.swift */; };
16A6B8F11CE7952600C27D5B /* RKProtocolProxyBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 16A6B8EF1CE7952600C27D5B /* RKProtocolProxyBase.h */; settings = {ATTRIBUTES = (Public, ); }; };
16A6B8F21CE7952600C27D5B /* RKProtocolProxyBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 16A6B8EF1CE7952600C27D5B /* RKProtocolProxyBase.h */; settings = {ATTRIBUTES = (Public, ); }; };
16A6B8F31CE7952600C27D5B /* RKProtocolProxyBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 16A6B8EF1CE7952600C27D5B /* RKProtocolProxyBase.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand All @@ -23,7 +25,6 @@
16C33B841BEFBAC900A0DBE0 /* ReactiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
16C33B851BEFBAC900A0DBE0 /* ReactiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
EC2515771CBD4E0700175926 /* StreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2515751CBD4E0000175926 /* StreamTests.swift */; };
EC25158D1CBF94B400175926 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC25158C1CBF94B400175926 /* Helpers.swift */; };
EC25158F1CBFB6A400175926 /* ArrayDiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A9A171CABDA5F0042A6AD /* ArrayDiffTests.swift */; };
EC65A0B31CBFEE4D00B41FA7 /* OperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC65A0B21CBFEE4D00B41FA7 /* OperationTests.swift */; };
EC8A99E71CABD9B50042A6AD /* CollectionProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */; };
Expand Down Expand Up @@ -96,14 +97,15 @@
162CB7461CB451D200FB6375 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
1671707E1BF4F62A001786CE /* ReactiveKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = ReactiveKit.podspec; sourceTree = SOURCE_ROOT; };
1671707F1BF4F64E001786CE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = README.md; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.markdown; };
168103431D046388007D1DD4 /* CollectionPropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CollectionPropertyTests.swift; path = Tests/CollectionPropertyTests.swift; sourceTree = SOURCE_ROOT; };
168103441D046388007D1DD4 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Helpers.swift; path = Tests/Helpers.swift; sourceTree = SOURCE_ROOT; };
16A6B8EF1CE7952600C27D5B /* RKProtocolProxyBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKProtocolProxyBase.h; sourceTree = "<group>"; };
16A6B8F01CE7952600C27D5B /* RKProtocolProxyBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKProtocolProxyBase.m; sourceTree = "<group>"; };
16A6B8F91CE7957400C27D5B /* ProtocolProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProtocolProxy.swift; path = Sources/ProtocolProxy.swift; sourceTree = SOURCE_ROOT; };
16C33AF91BEFB72500A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
16C33B161BEFB9CB00A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
16C33B241BEFBA0100A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
EC2515751CBD4E0000175926 /* StreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StreamTests.swift; path = Tests/StreamTests.swift; sourceTree = SOURCE_ROOT; };
EC25158C1CBF94B400175926 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
EC65A0B21CBFEE4D00B41FA7 /* OperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OperationTests.swift; path = Tests/OperationTests.swift; sourceTree = SOURCE_ROOT; };
EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CollectionProperty.swift; path = Sources/CollectionProperty.swift; sourceTree = SOURCE_ROOT; };
EC8A99DC1CABD9B50042A6AD /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Disposable.swift; path = Sources/Disposable.swift; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -223,7 +225,8 @@
EC8A9A181CABDA5F0042A6AD /* PerformanceTests.swift */,
EC2515751CBD4E0000175926 /* StreamTests.swift */,
EC65A0B21CBFEE4D00B41FA7 /* OperationTests.swift */,
EC25158C1CBF94B400175926 /* Helpers.swift */,
168103431D046388007D1DD4 /* CollectionPropertyTests.swift */,
168103441D046388007D1DD4 /* Helpers.swift */,
ECBCCDE11BEB6B9B00723476 /* Info.plist */,
);
path = ReactiveKitTests;
Expand Down Expand Up @@ -540,9 +543,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EC25158D1CBF94B400175926 /* Helpers.swift in Sources */,
168103451D046388007D1DD4 /* CollectionPropertyTests.swift in Sources */,
EC2515771CBD4E0700175926 /* StreamTests.swift in Sources */,
EC25158F1CBFB6A400175926 /* ArrayDiffTests.swift in Sources */,
168103461D046388007D1DD4 /* Helpers.swift in Sources */,
EC65A0B31CBFEE4D00B41FA7 /* OperationTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
2 changes: 1 addition & 1 deletion ReactiveKit/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.3</string>
<string>2.0.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
98 changes: 80 additions & 18 deletions Sources/CollectionProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public protocol CollectionChangesetType {
var updates: [Collection.Index] { get }
}

public struct CollectionChangeset<Collection: CollectionType>: CollectionChangesetType {
public struct CollectionChangeset<Collection: CollectionType>: CollectionChangesetType, CustomStringConvertible {
public let collection: Collection
public let inserts: [Collection.Index]
public let deletes: [Collection.Index]
Expand All @@ -46,6 +46,10 @@ public struct CollectionChangeset<Collection: CollectionType>: CollectionChanges
self.deletes = deletes
self.updates = updates
}

public var description: String {
return "Collection: \(collection), changes: i\(inserts), d\(deletes), u\(updates)."
}
}

public extension CollectionChangeset {
Expand Down Expand Up @@ -74,17 +78,42 @@ public extension CollectionChangesetType where Collection.Index == Int {
public extension CollectionChangesetType where Collection.Index == Int {

/// O(n)
public func filter(include: Collection.Generator.Element -> Bool) -> CollectionChangeset<Array<Collection.Generator.Element>> {
public func filter(previousIndexMap: [Int: Int], include: Collection.Generator.Element -> Bool) -> ([Int: Int], CollectionChangeset<Array<Collection.Generator.Element>>?) {
var filtered: [Collection.Generator.Element] = []
var indexMap: [Int: Int] = [:]
var iterator = 0

filtered.reserveCapacity(collection.count)

for (index, element) in collection.enumerate() {
if include(element) {
filtered.append(element)
indexMap[index] = iterator
iterator += 1
}
}

let filteredPairs = zip(collection.indices, collection).filter { include($0.1) }
let includedIndices = Set(filteredPairs.map { $0.0 })
var filteredInserts = inserts.flatMap { indexMap[$0] }
var filteredDeletes = deletes.flatMap { previousIndexMap[$0] }
var filteredUpdates: [Int] = []

let filteredCollection = filteredPairs.map { $0.1 }
let filteredInserts = inserts.filter { includedIndices.contains($0) }
let filteredDeletes = deletes.filter { includedIndices.contains($0) }
let filteredUpdates = updates.filter { includedIndices.contains($0) }
for update in updates {
if let index = indexMap[update] {
if let _ = previousIndexMap[update] {
filteredUpdates.append(index)
} else {
filteredInserts.append(index)
}
} else if let index = previousIndexMap[update] {
filteredDeletes.append(index)
}
}

return CollectionChangeset(collection: filteredCollection, inserts: filteredInserts, deletes: filteredDeletes, updates: filteredUpdates)
if inserts.count + deletes.count + updates.count > 0 && filteredInserts.count + filteredDeletes.count + filteredUpdates.count == 0 {
return (indexMap, nil)
} else {
return (indexMap, CollectionChangeset(collection: filtered, inserts: filteredInserts, deletes: filteredDeletes, updates: filteredUpdates))
}
}
}

Expand All @@ -104,11 +133,23 @@ public extension CollectionChangesetType where Collection.Index: Hashable {
}

let sortedCollection = sortedPairs.map { $0.1 }
let newDeletes = deletes.map { previousSortMap![$0]! }
let newInserts = inserts.map { sortMap[$0]! }
let newUpdates = updates.map { sortMap[$0]! }
let changeSet = CollectionChangeset(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates)

var newDeletes = deletes.map { previousSortMap![$0]! }
var newInserts = inserts.map { sortMap[$0]! }
var newUpdates: [Int] = []

for update in updates {
if let index = sortMap[update], prevIndex = previousSortMap![update] {
if index == prevIndex {
newUpdates.append(index)
} else {
newDeletes.append(prevIndex)
newInserts.append(index)
}
}
}

let changeSet = CollectionChangeset(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates)
return (changeset: changeSet, sortMap: sortMap)
}
}
Expand All @@ -121,11 +162,23 @@ public extension CollectionChangesetType where Collection.Index: Equatable {
let sortedIndices = sortedPairs.map { $0.0 }

let sortedCollection = sortedPairs.map { $0.1 }
let newDeletes = deletes.map { previousSortedIndices!.indexOf($0)! }
let newInserts = inserts.map { sortedIndices.indexOf($0)! }
let newUpdates = updates.map { sortedIndices.indexOf($0)! }
let changeset = CollectionChangeset(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates)

var newDeletes = deletes.map { previousSortedIndices!.indexOf($0)! }
var newInserts = inserts.map { sortedIndices.indexOf($0)! }
var newUpdates: [Array<Collection.Generator.Element>.Index] = []

for update in updates {
if let index = sortedIndices.indexOf(update), prevIndex = previousSortedIndices!.indexOf(update) {
if index == prevIndex {
newUpdates.append(index)
} else {
newDeletes.append(prevIndex)
newInserts.append(index)
}
}
}

let changeset = CollectionChangeset(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates)
return (changeset: changeset, sortedIndices: sortedIndices)
}
}
Expand Down Expand Up @@ -246,8 +299,17 @@ public extension CollectionPropertyType where Collection.Index == Int {
@warn_unused_result
public func filter(include: Collection.Generator.Element -> Bool) -> Stream<CollectionChangeset<Array<Collection.Generator.Element>>> {
return Stream { observer in
var previousIndexMap: [Int: Int] = [:]
return self.observe { event in
observer.observer(event.map { $0.filter(include) })
if let changeset = event.element {
let (indexMap, filteredChanges) = changeset.filter(previousIndexMap, include: include)
previousIndexMap = indexMap
if let filteredChanges = filteredChanges {
observer.next(filteredChanges)
}
} else {
observer.completed()
}
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions Sources/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,20 @@ public extension OperationType {
}
}

extension OperationEvent: CustomStringConvertible {

public var description: String {
switch self {
case .Next(let element):
return ".Next(\(element))"
case .Failure(let error):
return ".Failure(\(error))"
case .Completed:
return ".Completed"
}
}
}

// MARK: - Operation

/// Represents a stream that can fail.
Expand Down
12 changes: 12 additions & 0 deletions Sources/Stream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ public extension StreamType {
}
}

extension StreamEvent: CustomStringConvertible {

public var description: String {
switch self {
case .Next(let element):
return ".Next(\(element))"
case .Completed:
return ".Completed"
}
}
}

// MARK: - Stream

/// Represents a stream over generic Element type.
Expand Down
Loading

0 comments on commit c6a672a

Please sign in to comment.