Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions FastDiff.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
284D483D23C697EA00DD2963 /* Diff+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284D483B23C6946800DD2963 /* Diff+UIKit.swift */; };
284D483E23C697F100DD2963 /* Diff+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284D483B23C6946800DD2963 /* Diff+UIKit.swift */; };
2889D0CA22D4D665000E7797 /* FastDiff.h in Headers */ = {isa = PBXBuildFile; fileRef = 2889D0C822D4D665000E7797 /* FastDiff.h */; settings = {ATTRIBUTES = (Public, ); }; };
2889D0CE22D4D675000E7797 /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Diffable.swift */; };
2889D0CF22D4D675000E7797 /* DiffingAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* DiffingAlgorithm.swift */; };
Expand Down Expand Up @@ -65,6 +67,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
284D483B23C6946800DD2963 /* Diff+UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diff+UIKit.swift"; sourceTree = "<group>"; };
2889D0C622D4D665000E7797 /* FastDiff.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FastDiff.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2889D0C822D4D665000E7797 /* FastDiff.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FastDiff.h; sourceTree = "<group>"; };
2889D0C922D4D665000E7797 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -186,7 +189,7 @@
name = Products;
sourceTree = BUILT_PRODUCTS_DIR;
};
OBJ_5 /* */ = {
OBJ_5 = {
isa = PBXGroup;
children = (
OBJ_6 /* Package.swift */,
Expand All @@ -197,7 +200,6 @@
2889D0C722D4D665000E7797 /* FastDiff */,
OBJ_25 /* Products */,
);
name = "";
sourceTree = "<group>";
};
OBJ_7 /* Sources */ = {
Expand All @@ -214,6 +216,7 @@
OBJ_9 /* Diffable.swift */,
OBJ_10 /* DiffingAlgorithm.swift */,
OBJ_11 /* InternalDiff.swift */,
284D483B23C6946800DD2963 /* Diff+UIKit.swift */,
);
name = FastDiffLib;
path = Sources/FastDiffLib;
Expand Down Expand Up @@ -348,9 +351,10 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = OBJ_5 /* */;
mainGroup = OBJ_5;
productRefGroup = OBJ_25 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -382,6 +386,7 @@
buildActionMask = 2147483647;
files = (
2889D0D022D4D675000E7797 /* InternalDiff.swift in Sources */,
284D483E23C697F100DD2963 /* Diff+UIKit.swift in Sources */,
2889D0CE22D4D675000E7797 /* Diffable.swift in Sources */,
2889D0CF22D4D675000E7797 /* DiffingAlgorithm.swift in Sources */,
);
Expand Down Expand Up @@ -410,6 +415,7 @@
buildActionMask = 0;
files = (
OBJ_49 /* Diffable.swift in Sources */,
284D483D23C697EA00DD2963 /* Diff+UIKit.swift in Sources */,
OBJ_50 /* DiffingAlgorithm.swift in Sources */,
OBJ_51 /* InternalDiff.swift in Sources */,
);
Expand Down
115 changes: 115 additions & 0 deletions Sources/FastDiffLib/Diff+UIKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// Diff+UIKit.swift
// AlgoChecker
//
// Created by Vijaya Prakash Kandel on 08.01.20.
//

import Foundation

/**
These below functions support the use of Diff naturally to UITableView or UICollectionView.
*/

internal func packingConsequetiveDeleteAddWithUpdate<T>(from diffResult: [DiffOperation<T>.Simple]) -> [DiffOperation<T>.Simple] {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add unit tests for this?

if diffResult.isEmpty { return [] }

var currentSeekIndex = 0 // This is the index that is not processed.

var accumulator: [DiffOperation<T>.Simple] = []
while currentSeekIndex < diffResult.count {
let thisItem = diffResult[currentSeekIndex]
let nextIndex = currentSeekIndex.advanced(by: 1)

if nextIndex < diffResult.count {
let nextItem = diffResult[nextIndex]
switch (thisItem, nextItem) {
case let (.delete(di, dIndex), .add(ai, aIndex)) where dIndex == aIndex:
let update = DiffOperation<T>.Simple.update(di, ai, dIndex)
accumulator.append(update)
default:
accumulator.append(thisItem)
accumulator.append(nextItem)
}
currentSeekIndex = nextIndex.advanced(by: 1)
} else {
// This is the last item
accumulator.append(thisItem)
// This breaks the iteration
currentSeekIndex = nextIndex
}
}
return accumulator
}


/**
Entire Tree/Graph diffing is possible.
However not something the library encourages due to added complexity O(n^2).
If you so choose to diff then please use this function.
*/
public func diffAllLevel<T>(_ oldContent: [T], _ newContent: [T]) -> [DiffOperation<T>] where T: Diffable, T.InternalItemType == T {
if oldContent.isEmpty && newContent.isEmpty { return [] }
var accumulator: [DiffOperation<T>] = []
let thisLevelDiff = diff(oldContent, newContent)
for index in thisLevelDiff {
if case let .update(o, n, _) = index {
accumulator = accumulator + diffAllLevel(o.children, n.children)
} else {
accumulator.append(index)
}
}
return accumulator
}


/**
Orders diff operation in way UIKit can process as is. Only orderedDiffOperations can be applied back to old items for merge.

This is a helper function which assumes that
1. Deletion happens first from end index on original [T]
2. Insertions follows
3. Update Follows

- Note: This is the case with UIKit (UITableView and UICollectionView dataSources)

- Limitation: Can't extend a protocol with a generic typed enum (generic type in general)
extension Array where Element: Operation<T> { }
*/
public func orderedOperation<T>(from operations: [DiffOperation<T>]) -> [DiffOperation<T>.Simple] {
/// Deletions need to happen from higher index to lower (to avoid corrupted indexes)
/// [x, y, z] will be corrupt if we attempt [d(0), d(2), d(1)]
/// d(0) succeeds then array is [x,y]. Attempting to delete at index 2 produces out of bounds error.
/// Therefore we sort in descending order of index
var deletions = [Int: DiffOperation<T>.Simple]()
var insertions = [DiffOperation<T>.Simple]()
var updates = [DiffOperation<T>.Simple]()

for oper in operations {
switch oper {
case let .update(item, newItem, index):
updates.append(.update(item, newItem, index))
case let .add(item, atIndex):
insertions.append(.add(item, atIndex))
case let .delete(item, from):
deletions[from] = .delete(item, from)
case let .move(item, from, to):
insertions.append(.add(item, to))
deletions[from] = .delete(item, from)
}
}
let descendingOrderedIndexDeletions = deletions.sorted(by: {$0.0 > $1.0 }).map{ $0.1 }
return descendingOrderedIndexDeletions + insertions + updates
}


/**
Optimizes the diff for easy usage for UIKit List (UITableView / UICollectionView) Datasources integration.

This takes care of:
- emitting ordered operations (needed to merge. This is what iOS datasources expect.)
- emitting optimized operation: consequetive add and delete on same index is replaced by update.
*/
public func diffOptimizingForUIKitUsage<T>(_ old: [T], new: [T]) -> [DiffOperation<T>.Simple] where T: Diffable {
return packingConsequetiveDeleteAddWithUpdate(from: orderedOperation(from: diff(old, new)))
}
29 changes: 0 additions & 29 deletions Sources/FastDiffLib/DiffingAlgorithm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,35 +253,6 @@ public func diff<T>(_ oldContent: [T], _ newContent: [T]) -> [DiffOperation<T>]
return operations
}

/** Limitation: Can't extend a protocol with a generic typed enum (generic type in general)
extension Array where Element: Operation<T> { }
*/
public func orderedOperation<T>(from operations: [DiffOperation<T>]) -> [DiffOperation<T>.Simple] {
/// Deletions need to happen from higher index to lower (to avoid corrupted indexes)
/// [x, y, z] will be corrupt if we attempt [d(0), d(2), d(1)]
/// d(0) succeeds then array is [x,y]. Attempting to delete at index 2 produces out of bounds error.
/// Therefore we sort in descending order of index
var deletions = [Int: DiffOperation<T>.Simple]()
var insertions = [DiffOperation<T>.Simple]()
var updates = [DiffOperation<T>.Simple]()

for oper in operations {
switch oper {
case let .update(item, newItem, index):
updates.append(.update(item, newItem, index))
case let .add(item, atIndex):
insertions.append(.add(item, atIndex))
case let .delete(item, from):
deletions[from] = .delete(item, from)
case let .move(item, from, to):
insertions.append(.add(item, to))
deletions[from] = .delete(item, from)
}
}
let descendingOrderedIndexDeletions = deletions.sorted(by: {$0.0 > $1.0 }).map{ $0.1 }
return descendingOrderedIndexDeletions + insertions + updates
}


extension Array where Element: Hashable {

Expand Down