Skip to content

Instantly share code, notes, and snippets.

@geor-kasapidi
Created February 27, 2020 19:45
Show Gist options
  • Select an option

  • Save geor-kasapidi/1b1f7b5d2c71cb58d3bee1e7622d8208 to your computer and use it in GitHub Desktop.

Select an option

Save geor-kasapidi/1b1f7b5d2c71cb58d3bee1e7622d8208 to your computer and use it in GitHub Desktop.
Diff example
public protocol Diffable: Equatable {
var diffId: String { get }
}
public enum Diff {
public enum Change: Hashable {
public typealias Index = Int
case delete(Index)
case insert(Index)
case update(Index, Index)
case move(Index, Index)
}
public typealias Changes = [Change]
private final class Entry {
var newCount: Int = 0
var oldIndices: ArraySlice<Int> = []
}
private struct Record {
let entry: Entry
var reference: Int?
}
private static func between<IdType: Hashable, ModelType>(_ oldItems: [ModelType],
_ newItems: [ModelType],
id: (ModelType) -> IdType,
isEqual: (ModelType, ModelType) -> Bool) -> Changes {
if oldItems.isEmpty && newItems.isEmpty {
return []
}
if oldItems.isEmpty {
return newItems.indices.map(Change.insert)
}
if newItems.isEmpty {
return oldItems.indices.map(Change.delete)
}
// -------------------------------------------------------
var table = [IdType: Entry]()
var newRecords = newItems.map { item -> Record in
let entry = table[id(item)] ?? Entry()
entry.newCount += 1
table[id(item)] = entry
return Record(entry: entry, reference: nil)
}
var oldRecords = oldItems.enumerated().map { (index, item) -> Record in
let entry = table[id(item)] ?? Entry()
entry.oldIndices.append(index)
table[id(item)] = entry
return Record(entry: entry, reference: nil)
}
table.removeAll()
// -------------------------------------------------------
newRecords.enumerated().forEach { (newIndex, newRecord) in
let entry = newRecord.entry
guard entry.newCount > 0, let oldIndex = entry.oldIndices.popFirst() else {
return
}
newRecords[newIndex].reference = oldIndex
oldRecords[oldIndex].reference = newIndex
}
// -------------------------------------------------------
var changes: [Change] = []
var offset = 0
let deleteOffsets = oldRecords.enumerated().map { (oldIndex, oldRecord) -> Int in
let deleteOffset = offset
if oldRecord.reference == nil {
changes.append(.delete(oldIndex))
offset += 1
}
return deleteOffset
}
// -------------------------------------------------------
offset = 0
newRecords.enumerated().forEach { (newIndex, newRecord) in
guard let oldIndex = newRecord.reference else {
changes.append(.insert(newIndex))
offset += 1
return
}
let deleteOffset = deleteOffsets[oldIndex]
let insertOffset = offset
let moved = (oldIndex - deleteOffset + insertOffset) != newIndex
let updated = !isEqual(newItems[newIndex], oldItems[oldIndex])
if updated {
changes.append(.update(oldIndex, oldIndex))
} else if moved {
changes.append(.move(oldIndex, newIndex))
}
}
// -------------------------------------------------------
return changes
}
public static func between<T: Diffable>(_ oldItems: [T], and newItems: [T]) -> Changes {
between(oldItems, newItems, id: { $0.diffId }, isEqual: { $0 == $1 })
}
public static func between<T: Hashable>(_ oldItems: [T], and newItems: [T]) -> Changes {
between(oldItems, newItems, id: { $0 }, isEqual: { $0 == $1 })
}
}
import Foundation
struct Person: Diffable {
let diffId = UUID().uuidString
let name: String
var age: UInt
init(name: String, age: UInt) {
self.name = name
self.age = age
}
// func hash(into hasher: inout Hasher) {
// hasher.combine(id)
// }
}
func testUpdate() {
let pavel = Person(name: "Pavel", age: 31)
let alexey0 = Person(name: "Alexey", age: 32)
var dmitriy = Person(name: "Dmitry", age: 26)
let alexey1 = Person(name: "Alexey", age: 22)
let saveliy = Person(name: "Saveliy", age: 23)
let arkadiy = Person(name: "Arkadiy", age: 32)
let fedor = Person(name: "Fedor", age: 24)
let team1 = [pavel, alexey0, dmitriy, alexey1, saveliy, arkadiy, fedor]
dmitriy.age += 1
let team2 = [pavel, alexey0, dmitriy, alexey1, saveliy, arkadiy, fedor]
let difference = Diff.between(team1, and: team2)
print(difference)
}
testUpdate()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment