using System.Collections;
using System.Collections.Generic;
public enum EditOperationKind {
public sealed class EditOperation<T>
: IEquatable<EditOperation<T>> {
internal string ToReport() {
EditOperationKind.None => $"Keep '{Before}', ({Cost})",
EditOperationKind.Insert => $"Insert '{After}', ({Cost})",
EditOperationKind.Delete => $"Delete '{Before}', ({Cost})",
EditOperationKind.Edit => $"Edit '{Before}' into '{After}', ({Cost})",
_ => $"??? '{Before}' into '{After}', ({Cost})",
internal EditOperation(EditOperationKind kind, T before, T after, double cost)
public EditOperationKind Kind { get; }
public double Cost { get; }
public override string ToString() {
EditOperationKind.None => $"Keep '{Before}'",
EditOperationKind.Insert => $"Insert '{After}'",
EditOperationKind.Delete => $"Delete '{Before}'",
EditOperationKind.Edit => $"Edit '{Before}' into '{After}'",
_ => $"Unknown '{Before}' into '{After}'",
public static bool operator ==(EditOperation<T> left, EditOperation<T> right) {
if (ReferenceEquals(left, right))
else if (left is null || right is null)
return left.Equals(right);
public static bool operator !=(EditOperation<T> left, EditOperation<T> right) {
if (ReferenceEquals(left, right))
else if (left is null || right is null)
return !left.Equals(right);
#region IEquatable<EditOperation<T>
public bool Equals(EditOperation<T> other) {
if (ReferenceEquals(this, other))
return Equals(Before, other.Before) &&
Equals(After, other.After) &&
public override bool Equals(object obj) =>
obj is EditOperation<T> other && Equals(other);
public override int GetHashCode() {
return (Before is null ? 0 : Before.GetHashCode()) ^
(After is null ? 0 : After.GetHashCode()) ^
#endregion IEquatable<EditOperation<T>
public interface IEditCost<T> {
double InsertionPrice(T value);
double DeletionPrice(T value);
double EditPrice(T from, T to);
public static class EditCosts<T> {
private class UniformEditCost : IEditCost<T> {
public double DeletionPrice(T value) => 1.0;
public double EditPrice(T from, T to) => Equals(from, to) ? 0 : 1.0;
public double InsertionPrice(T value) => 1.0;
private class InsertDeleteEditCost : IEditCost<T> {
public double DeletionPrice(T value) => 1.0;
public double EditPrice(T from, T to) => Equals(from, to) ? 0 : 2.0;
public double InsertionPrice(T value) => 1.0;
public static IEditCost<T> Uniform {
} = new UniformEditCost();
public static IEditCost<T> Standard {
} = new InsertDeleteEditCost();
public sealed class EditProcedure<T> : IReadOnlyList<EditOperation<T>> {
private List<EditOperation<T>> m_Sequence;
private void CorePerform(T[] source,
Func<T, double> insertCost,
Func<T, double> deleteCost,
Func<T, T, double> editCost) {
EditOperationKind[][] M = Enumerable
.Range(0, source.Length + 1)
.Select(line => new EditOperationKind[target.Length + 1])
double[][] D = Enumerable
.Range(0, source.Length + 1)
.Select(line => new double[target.Length + 1])
for (int i = 1; i <= source.Length; ++i) {
M[i][0] = EditOperationKind.Delete;
D[i][0] = (sum += deleteCost(source[i - 1]));
for (int i = 1; i <= target.Length; ++i) {
M[0][i] = EditOperationKind.Insert;
D[0][i] = (sum += insertCost(target[i - 1]));
for (int i = 1; i <= source.Length; ++i)
for (int j = 1; j <= target.Length; ++j) {
double insert = D[i][j - 1] + insertCost(target[j - 1]);
double delete = D[i - 1][j] + deleteCost(source[i - 1]);
double edit = D[i - 1][j - 1] + editCost(source[i - 1], target[j - 1]);
double min = Math.Min(Math.Min(insert, delete), edit);
M[i][j] = EditOperationKind.Insert;
M[i][j] = EditOperationKind.Delete;
M[i][j] = object.Equals(source[i - 1], target[j - 1])
: EditOperationKind.Edit;
EditDistance = D[source.Length][target.Length];
new List<EditOperation<T>>(source.Length + target.Length);
for (int x = target.Length, y = source.Length; (x > 0) || (y > 0);) {
EditOperationKind op = M[y][x];
if (op == EditOperationKind.Insert) {
m_Sequence.Add(new EditOperation<T>(op, default, target[x], D[y][x + 1] - D[y][x]));
else if (op == EditOperationKind.Delete) {
m_Sequence.Add(new EditOperation<T>(op, source[y], default, D[y + 1][x] - D[y][x]));
else if (op == EditOperationKind.Edit || op == EditOperationKind.None) {
m_Sequence.Add(new EditOperation<T>(op, source[y], target[x], D[y + 1][x + 1] - D[y][x]));
public EditProcedure(IEnumerable<T> source,
Func<T, double> insertCost,
Func<T, double> deleteCost,
Func<T, T, double> editCost) {
throw new ArgumentNullException(nameof(source));
throw new ArgumentNullException(nameof(target));
else if (insertCost is null)
throw new ArgumentNullException(nameof(insertCost));
else if (deleteCost is null)
throw new ArgumentNullException(nameof(deleteCost));
else if (editCost is null)
throw new ArgumentNullException(nameof(editCost));
CorePerform(source.ToArray(),
public EditProcedure(IEnumerable<T> source,
throw new ArgumentNullException(nameof(source));
throw new ArgumentNullException(nameof(target));
throw new ArgumentNullException(nameof(price));
Func<T, double> insertCost = price.InsertionPrice;
Func<T, double> deleteCost = price.DeletionPrice;
Func<T, T, double> editCost = price.EditPrice;
CorePerform(source.ToArray(),
public double EditDistance {
public IReadOnlyList<EditOperation<T>> EditSequence => m_Sequence;
public override string ToString() {
return string.Join(Environment.NewLine,
$"Distance: {EditDistance}",
$"Sequence ({m_Sequence.Count} steps):",
string.Join(Environment.NewLine, m_Sequence
.Select(item => $" {item.ToReport()}")));
#region IReadOnlyList<EditOperation<T>>
public int Count => m_Sequence.Count;
public EditOperation<T> this[int index] => m_Sequence[index];
public IEnumerator<EditOperation<T>> GetEnumerator() => m_Sequence.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => m_Sequence.GetEnumerator();
#endregion IReadOnlyList<EditOperation<T>>
public static partial class EnumerableExtensions {
public static EditProcedure<T> ToEditProcedure<T>(this IEnumerable<T> source,
Func<T, double> insertCost,
Func<T, double> deleteCost,
Func<T, T, double> editCost) {
return new EditProcedure<T>(source, target, insertCost, deleteCost, editCost);
public static EditProcedure<T> ToEditProcedure<T>(this IEnumerable<T> source,
return new EditProcedure<T>(source,
(x, y) => object.Equals(x, y) ? 0 : editCost);
public static EditProcedure<T> ToEditProcedure<T>(this IEnumerable<T> source,
return new EditProcedure<T>(source, target, prices);
public static class Weights {
private static readonly IReadOnlyList<string> s_Rows = new [] {
public static (int row, int column) Find(char letter) {
letter = char.ToLower(letter);
for (int r = 0; r < s_Rows.Count; ++r) {
int c = s_Rows[r].IndexOf(letter);
public static int GetWeightedDistance(char left, char right) {
return Math.Abs(a.row - b.row) + Math.Abs(a.column - b.column);
public static class Program {
public static void Main() {
string a = "abracadabra";
string b = "abbreviation";
var edit = a.ToEditProcedure(b,
(a, b) => Weights.GetWeightedDistance(a, b));
Console.WriteLine($"{edit.EditDistance}");