Simplify to only count the diffs.
diff --git a/lib/src/calculate_difference.dart b/lib/src/calculate_difference.dart
deleted file mode 100644
index c3b53bd..0000000
--- a/lib/src/calculate_difference.dart
+++ /dev/null
@@ -1,127 +0,0 @@
-/// Determines the set of Levenshtein edits needed to convert [before] into
-/// [after].
-///
-/// Returns the list of differences.
-/// From: https://en.wikipedia.org/wiki/Levenshtein_distance
-List<Diff<T>> calculateDiffs<T>(List<T> before, List<T> after) {
-  var matrix = _Matrix(before.length + 1, after.length + 1);
-
-  // Initialize the first row of distances. It's the number of edits to get
-  // from an empty expected list to the prefix of the actual list of a given
-  // length, which is just that many inserts.
-  for (var x = 0; x < before.length + 1; x++) {
-    matrix.set(x, 0, x);
-  }
-
-  // For each prefix of the after list, calculate the edit distances to reach
-  // it from each prefix of the before list. Each row is calculated from the
-  // previous row.
-  for (var y = 1; y < after.length + 1; y++) {
-    // The first element of v1 is A[i+1][0].
-    // The edit distance is delete (i+1) elements from s to match empty t.
-    matrix.set(0, y, y);
-
-    // Use formula to fill in the rest of the row.
-    for (var x = 1; x < before.length + 1; x++) {
-      var cost = after[y - 1] == before[x - 1] ? 0 : 1;
-
-      var left = matrix.get(x - 1, y) + 1;
-      var up = matrix.get(x, y - 1) + 1;
-      var diagonal = matrix.get(x - 1, y - 1) + cost;
-      matrix.set(x, y, _min3(left, up, diagonal));
-    }
-  }
-
-  var x = matrix.width - 1;
-  var y = matrix.height - 1;
-  var edits = <Diff<T>>[];
-  while (x > 0 || y > 0) {
-    var here = matrix.get(x, y);
-
-    var left = x > 0 ? matrix.get(x - 1, y) : here + 999;
-    var up = y > 0 ? matrix.get(x, y - 1) : here + 999;
-    var diagonal = x > 0 && y > 0 ? matrix.get(x - 1, y - 1) : here + 999;
-
-    if (diagonal <= left && diagonal <= up) {
-      // We're consuming an element from both.
-      if (diagonal != here) {
-        // And they didn't match, so substitute.
-        edits.add(SubstituteDiff(before[x - 1], after[y - 1]));
-      }
-      x--;
-      y--;
-    } else if (left <= up) {
-      // We're consuming an element from before, so there is a new element.
-      edits.add(InsertDiff(before[x - 1]));
-      x--;
-    } else {
-      // We're consuming an element from after, so there is a removed element.
-      edits.add(DeleteDiff(after[y - 1]));
-      y--;
-    }
-  }
-
-  return edits.reversed.toList();
-}
-
-/// Determines the number of Levenshtein edits needed to convert [before] into
-/// [after].
-///
-/// Treates a substitution as a single edit and not a delete + insert.
-int countDifferences<T>(
-  List<T> before,
-  List<T> after, {
-  bool Function(T, T)? compare,
-}) => calculateDiffs(before, after).length;
-
-sealed class Diff<T> {}
-
-final class SubstituteDiff<T> extends Diff<T> {
-  final T before;
-  final T after;
-
-  SubstituteDiff(this.before, this.after);
-}
-
-final class InsertDiff<T> extends Diff<T> {
-  final T after;
-
-  InsertDiff(this.after);
-}
-
-final class DeleteDiff<T> extends Diff<T> {
-  final T before;
-
-  DeleteDiff(this.before);
-}
-
-/// Returns the minimum of [a], [b], and [c].
-int _min3(int a, int b, int c) {
-  if (a < b) {
-    return a < c ? a : c;
-  } else {
-    return b < c ? b : c;
-  }
-}
-
-/// A two-dimensional fixed-size matrix of integers.
-class _Matrix {
-  /// The number of elements in a row of the matrix.
-  final int width;
-
-  /// The number of elements in a column of the matrix.
-  final int height;
-
-  final List<int> _elements;
-
-  /// Creates a new matrix with [width], [height] value initialized to `0`.
-  _Matrix(this.width, this.height) : _elements = List.filled(width * height, 0);
-
-  /// Gets the element in the array at [x], [y].
-  int get(int x, int y) => _elements[y * width + x];
-
-  /// Sets the value in the matrix at [x], [y] to [value].
-  void set(int x, int y, int value) {
-    _elements[y * width + x] = value;
-  }
-}
diff --git a/lib/src/cli/summary.dart b/lib/src/cli/summary.dart
index 1ca4090..cd4bb8d 100644
--- a/lib/src/cli/summary.dart
+++ b/lib/src/cli/summary.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 import 'dart:io';
 
-import '../calculate_difference.dart';
 import '../profile.dart';
 import '../source_code.dart';
 import 'formatter_options.dart';
@@ -84,7 +83,7 @@
     var inputLines = input.text.split('\n');
     var outputLines = output.text.split('\n');
     _lines += inputLines.length;
-    _changedLines += countDifferences(inputLines, outputLines);
+    _changedLines += _countDifferences(inputLines, outputLines);
   }
 
   /// Show the times for the slowest files to format.
@@ -107,6 +106,60 @@
       '$_changedLines out of $_lines lines changed ($linePercent%).',
     );
   }
+
+  /// Determines the Levenshtein edit distance to convert [before] into [after].
+  ///
+  /// That means the number of single-element insertions, deletions, or
+  /// substitutions required to turn [before] into [after], treating
+  /// substitution as a single edit.
+  ///
+  /// Uses the Wagner-Fischer two-row algorithm:
+  /// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
+  int _countDifferences(List<String> before, List<String> after) {
+    var previousRow = List.filled(before.length + 1, 0);
+    var currentRow = List.filled(before.length + 1, 0);
+
+    // Initialize the first row of distances. It's the number of edits to get
+    // from an empty expected list to the prefix of the actual list of a given
+    // length, which is just that many inserts.
+    for (var x = 0; x < before.length + 1; x++) {
+      previousRow[x] = x;
+    }
+
+    // For each prefix of the after list, calculate the edit distances to reach
+    // it from each prefix of the before list. Each row is calculated from the
+    // previous row.
+    for (var y = 1; y < after.length + 1; y++) {
+      // The first element of v1 is A[i+1][0].
+      // The edit distance is delete (i+1) elements from s to match empty t.
+      currentRow[0] = y;
+
+      // Use formula to fill in the rest of the row.
+      for (var x = 1; x < before.length + 1; x++) {
+        var cost = after[y - 1] == before[x - 1] ? 0 : 1;
+
+        var left = currentRow[x - 1] + 1;
+        var up = previousRow[x] + 1;
+        var diagonal = previousRow[x - 1] + cost;
+        currentRow[x] = _min3(left, up, diagonal);
+      }
+
+      // Swap the rows so the current one become the new previous one and the old
+      // previous row is reused as the new current row.
+      (previousRow, currentRow) = (currentRow, previousRow);
+    }
+
+    return previousRow.last;
+  }
+
+  /// Returns the minimum of [a], [b], and [c].
+  int _min3(int a, int b, int c) {
+    if (a < b) {
+      return a < c ? a : c;
+    } else {
+      return b < c ? b : c;
+    }
+  }
 }
 
 /// Tracks how many files were formatted and the total time.