A blog about **how Swift works** and **iOS tricks**, by Bruno Rocha.

Articles about advanced Swift development in excruciating detail, for free!

# How Collection Diffing works in Swift

**Ordered Collection Diffing** is a feature added in Swift 5.1 that allows you to calculate and apply the difference between two collections. Using diffing libraries is common in iOS for a few reasons, the most popular one being to handle the addition and removal of elements in `UITableViews`

. With the addition of this feature, developers can now diff `Collections`

without having to bother with external libraries.

Have you wondered what efficiently diffing something looks like? In this article, we'll see and analyze the diffing APIs as well as the **Myers's Diffing Algorithm** used by the Swift Standard Library -- the same used in **git**.

## Collection Diffing APIs

The diffing APIs are available as an extension for any `BidirectionalCollection`

, with an abstracted helper type for ones whose's elements are `Equatable`

:

`extension BidirectionalCollection {`

`public func difference<C: BidirectionalCollection>(`

`from other: C,`

`by areEquivalent: (C.Element, Element) -> Bool`

`) -> CollectionDifference<Element>`

`where C.Element == Self.Element {`

`return ... // Article spoilers removed!`

`}`

`}`

`extension BidirectionalCollection where Element: Equatable {`

`public func difference<C: BidirectionalCollection>(`

`from other: C`

`) -> CollectionDifference<Element> where C.Element == Self.Element {`

`return difference(from: other, by: ==)`

`}`

`}`

The return type for diffing operations is an `CollectionDifference`

object -- basically an array of "moves" necessary to turn the first collection into the second one. The "moves" are represented as an enum of additions and removals, and the `CollectionDifference`

object holds all of them.

`public struct CollectionDifference<ChangeElement> {`

`/// A single change to a collection.`

`@frozen`

`public enum Change {`

`case insert(offset: Int, element: ChangeElement, associatedWith: Int?)`

`case remove(offset: Int, element: ChangeElement, associatedWith: Int?)`

`}`

`public let insertions: [Change]`

`public let removals: [Change]`

`}`

`CollectionDifference`

is a `Collection`

itself, which means that it can be iterated. **An important aspect of this is that the iteration will happen in a specific order: First, all the removals from highest to lowest offset, followed by insertions from lowest to highest.** This is both for performance reasons (removing elements from the end of an array is faster from the beginning, and adding elements to the front when there are fewer elements is faster than when there's a lot), and for visual reasons as we'll see when the underlying algorithm is inspected.

We can see this behavior with a real example. Let's calculate the difference between these two strings:

`let a = "ABCABBA"`

`let b = "CBABAC"`

There are tons of ways of transforming `a`

into `b`

, but our interest is doing so in the least amount of steps. When calling `difference(_:)`

and iterating the result, we'll see one of these solutions:

`let diff = b.difference(from: a)`

`diff.forEach { print($0) }`

`// remove(offset: 5, element: "B", associatedWith: nil)`

`// remove(offset: 1, element: "B", associatedWith: nil)`

`// remove(offset: 0, element: "A", associatedWith: nil)`

`// insert(offset: 1, element: "B", associatedWith: nil)`

`// insert(offset: 5, element: "C", associatedWith: nil)`

(We'll leave the explanation of the algorithm itself for the last section because it's a bit complicated.)

From here, you can use the resulting `CollectionDifference`

object as you see fit, like animating the addition and removal of rows from an `UITableView`

. The neat thing is that Swift provides an `applying(_:)`

method that allows you to directly apply a `CollectionDifference`

object into a `Collection`

:

`let newB = a.applying(diff)`

`//CBABAC`

This particular example looks useless, so here's something where applying diffs is useful: The same three-way merge process that **git** uses as a strategy to merge commits!

`// Split the contents of the sources into lines`

`let baseLines = base.components(separatedBy: "\n")`

`let theirLines = theirs.components(separatedBy: "\n")`

`let myLines = mine.components(separatedBy: "\n")`

`// Create a difference from base to theirs`

`let diff = theirLines.difference(from:baseLines)`

`// Apply it to mine, if possible`

`guard let patchedLines = myLines.applying(diff) else {`

`print("Merge conflict applying patch, manual merge required")`

`return`

`}`

`// Reassemble the result`

`let patched = patchedLines.joined(separator: "\n")`

`print(patched)`

## How applying() works

The `applying(_:)`

method works by creating a new, empty `Collection`

instance that gets filled as the changes are iterated. Because the changes are iterated in the specific high removals -> low removals -> low insertions -> high insertions, the algorithm is able to build the final result through ranges instead of individual elements. For example, if you have a 10 character string and the first step is to remove the 5th character, we know right off the bat that the final result will contain the range `5...9`

in that same order, so "removing the 5th character" is equivalent to adding everything that comes after it. The same logic is applied until all the changes are processed. Because of the interaction with ranges, `applying(_:)`

is only available to `RangeReplaceableCollections`

.

It is possible for this process to fail, for example, when the desired element to be removed doesn't exist, which is possible in cases like the **git** example due to conflicting information in the data. In these cases, `applying(_:)`

returns `nil`

.

The actual method is a bit long and hard to read, so I'll leave the link to it instead of showing it here.

## The complex part: How difference() works

Now that we've seen how the APIs work, we're ready to take a look at what Swift does to generate the final `CollectionDifference`

object.

As stated before, there are many ways to transform a `Collection`

into another one. For example, to transform `a: ABCABBA`

to `b: CBABAC`

, we could simply remove every single element from `a`

and append the ones from `b`

. Easy peasy!

However, at a best case of `a.count + b.count`

"moves", that would be very inefficient. One way to find the best way possible to do this change is to transform this into a **graph problem**. Imagine that we have a **matrix** where the columns are characters from `a`

and rows are characters from `b`

:

`- A B C A B B A`

`-`

`C`

`B`

`A`

`B`

`A`

`C`

In this matrix, the top-left position 0,0 represents the original `ABCABBA`

string, while the bottom-right position represents the final `CBABAC`

string. Moving **right** in this matrix represents **removing** an element from the original string, while moving **down** represents **adding** an element from the new one. Each of these movements counts as a step, but if the elements from both strings at a specific position **match** (like the position 1,3 - two Cs), you can **diagonally move down and to the right** for "free", as no changes are necessary for that position.

Given this description, we can transform this problem into another one: **How can we reach the bottom right of the matrix in the least amount of steps (a.k.a maximizing the number of diagonal moves)?**

If you ever studied graphs you might remember that finding the shortest path between two nodes in an unweighted graph is a classical computer science problem! This problem is solved by the popular graph search algorithm Breadth-First Search -- because the graph is unweighted, taking turns on checking the neighbors of each node is guaranteed to discover the shortest path between a source node to a target one. Applying the algorithm in the previous example will show us a path that transforms our string in **5** moves (remember that diagonal moves do not count as moves):

`- A B C A B B A`

`- 0 1 2`

`C 2 3`

`B 3`

`A 4`

`B 4`

`A 4`

`C 5`

The implementation behind the true diffing algorithms is very similar to this, but with a few (unfortunately, relatively complicated) important twists. Although we're looking for the shortest edit path between two `Collections`

, we're not exactly looking for **any** short path. Especially for visual applications like **git**, the overall order of how deletions and insertions are present is important. When you're looking at the merge diff in git, you would expect the diff to be somewhat "intelligent" in terms of knowing what was really added and deleted by your changes instead of randomly saying that things were "added" and "deleted" because it fulfills the shortest path condition.

`Good: class Foo { Bad: class Foo {`

`init(bar: String) { init(bar: String) {`

`self.bar = bar self.bar = bar`

`} + }`

`+ +`

`+ func inspect() { + func inspect() {`

`+ print(bar) + print(bar)`

`+ } }`

`} }`

Diffing algorithms carefully choose the paths that Breadth-First Search is going to traverse in order to provide a meaningful diff.

## Swift's Diffing Algorithm: Myers's Diffing Algorithm

Swift uses Myers's Diffing Algorithm (paper here) as part of the diffing APIs. Given two collections, the result is the **Shortest Edit Script** that transforms A into B. Much like the abstracted `CollectionDifference`

type, the Shortest Edit Script is a list of additions and removals. In order to provide a good diff, Myers's prefers deleting over inserting, and it differs from Breadth-First Search by prioritizing what seems to be "good" paths instead of blindly switching nodes like the original algorithm.

This is done by adding two additional components to the matrix: How **deep** we are into the graph (number of moves, in the X-axis), and the **ratio** of additions versus deletions, which is a value **k** that increases by one every time we remove a value (moving right), and decreases by one every time we add a value (moving down), in the Y-axis. This is better seen if we shift a graph by 45 degrees (note that this graph doesn't contain the diagonal connections that make the paths shorter):

`| 0 1 2 3 4 5`

`+--------------------------------------`

`5 | 5,0`

`| /`

`4 | 4,0`

`| / \`

`3 | 3,0 4,1`

`| / \ /`

`2 | 2,0 3,1`

`| / \ / \`

`1 | 1,0 2,1 3,2`

`| / \ / \ / ...`

`0 | 0,0 1,1 2,2`

`| \ / \ / \`

`-1 | 0,1 1,2 2,3`

`| \ / \ /`

`-2 | 0,2 1,3`

`| \ / \`

`-3 | 0,3 ... 1,4`

To find the perfect path, the algorithm iterates the possible **depth** values. For each **depth**, it iterates the possible **k values** at that depth (`-d...d`

) and determines the best move it can make at that point for that **k value**.

The best move is determined based on a few rules we'll see soon, but the important part here is that this is done based on the decisions made on the previous depth iteration. To make the current iteration not mess with the previous moves, the **k values** are iterated in steps of 2.

`var result = [KValues]()`

`var currentKValues = KValues()`

`currentKValues[1] = 0 // Ignore this for now, explained later!`

`outer: for d in 0...(a.count + b.count) {`

`result.append(currentKValues)`

`let previousKValues = currentKValues`

`currentKValues = KValues()`

`for k in stride(from: -d, through: d, by: 2)`

`// Determine the best moves for the current K values`

`}`

`}`

`return backtrackPath(fromKValues: result) // Additions and Deletions`

"Determining" the best move means deciding to go right or down from the positions recorded in the previous depth iteration. We should always move right if `k == -d`

(bottom of the currently "accessible" graph), and always down if `k == +d`

(right edge of the currently "accessible" graph). Otherwise, we should pick the position based on the current adjacent k's best values that leads us to the highest x value -- this way, we prioritize deletion.

`let currX: Int`

`if k == -d {`

`currX = previousKValues[k+1] // Moving down`

`} else if k == d {`

`currX = previousKValues[k-1] + 1 // Moving to the right`

`} else if previousKValues[k-1] < previousKValues[k+1] {`

`currX = previousKValues[k+1] // Moving down`

`} else {`

`currX = previousKValues[k-1] + 1 // Moving to the right`

`}`

`let currY = currX - k`

This loop is the reason why we need to start the algorithm with the `1`

**k value** set to 0. This makes sure that the first iteration determines that the "first best move" is to go down to 0,0 -- the beginning of the graph. This allows everything else to proceed as normal.

Once the best move is found, we try to move diagonally as much as we can to claim our free steps.

`while currX < a.count && currY < b.count {`

`if a[currX] == b[currY] {`

`break`

`}`

`x &+= 1`

`y &+= 1`

`}`

Finally, we store the best X value we've found for the current **k value** and halt the algorithm if we've reached the bottom right. Note how we only store X: Because **k** is a ratio of X and Y, we can infer Y from it and thus do not need to store Y.

`currentKValues[k] = currX`

`if currX >= a.count && currY >= b.count {`

`break outer`

`}`

You might wonder how subscripting `currentKValues`

works if k can be negative -- the Standard Library created a custom type that translates such negative indexes into positive ones. The idea is to create an array long enough to fit both positive and negative values and treat the indexes inside.

The complete algorithm from Swift can be found here.

The result of this implementation of Myers's is an array representing how far each **k value** has traveled in the X-axis in a specific depth. To get the actual path, all we have to do is backtrack from the only **k value** that has reached the maximum depth. This is done through a similar loop: For each current value, move up or to the left based on who has the largest X value in the previous iteration of the original method. Here's how this is implemented in the Standard Library (with `_V`

being their version of my `KValues`

type):

`// Backtrack through the trace generated by the Myers descent to produce the changes that make up the diff`

`func _formChanges(`

`from a: UnsafeBufferPointer<C.Element>,`

`to b: UnsafeBufferPointer<C.Element>,`

`using trace: [_V]`

`) -> [CollectionDifference<C.Element>.Change] {`

`var changes = [CollectionDifference<C.Element>.Change]()`

`changes.reserveCapacity(trace.count)`

`var x = a.count`

`var y = b.count`

`for d in stride(from: trace.count &- 1, to: 0, by: -1) {`

`let v = trace[d]`

`let k = x &- y`

`let prev_k = (k == -d || (k != d && v[k &- 1] < v[k &+ 1])) ? k &+ 1 : k &- 1`

`let prev_x = v[prev_k]`

`let prev_y = prev_x &- prev_k`

`while x > prev_x && y > prev_y {`

`// No change at this position.`

`x &-= 1`

`y &-= 1`

`}`

`if y != prev_y {`

`changes.append(.insert(offset: prev_y, element: b[prev_y], associatedWith: nil))`

`} else {`

`changes.append(.remove(offset: prev_x, element: a[prev_x], associatedWith: nil))`

`}`

`x = prev_x`

`y = prev_y`

`}`

`return changes`

`}`

Now that we have the final `CollectionDifference`

type, we can finally return it and conclude the diffing algorithm. The worst case for Myers's is `O(a * b)`

as it is possible for us to traverse all of the nodes of the graph.

## Conclusion

As always, I like to finish these by saying that knowing what's inside of what you're using allows you to make better decisions in code. Different algorithms are meant for different use-cases, and knowing how to make trade-offs is useful everywhere. Plus, you get to learn cool little pieces of trivia -- did you know that Myers's is one of the merging strategies in **git**? :)

## References and Good Reads

Myers's PaperDiffing.swift

CollectionDifference.swift

The Myers Diff Algorithm Explanation - Great read!

Articles about advanced Swift development in excruciating detail, for free!