enum Action { Initial, Unchanged, Insert, Delete }
int defaultEditCost(char ch) => char.ToLower(ch) - 'a' + 1;
int editDistancePalindrime(string str, Func<char, int> costFn)
{
// Calculate the levenshtein distance table between `str` and its reverse.
// str[i-1] is the normal string, and str[n-j] is the reverse.
int n = str.Length;
var table = new (Action action, int totalCost, int actionCost)[n + 1, n + 1];
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= n; j++)
{
if (i == 0 && j == 0) table[i, j] = (Action.Initial, 0, 0);
else if (i == 0)
{
var insertCost = costFn(str[n - j]);
var insertTotalCost = table[i, j - 1].totalCost + insertCost;
table[i, j] = (Action.Insert, insertTotalCost, insertCost);
}
else if (j == 0)
{
var deleteCost = costFn(str[i - 1]);
var deleteTotalCost = table[i - 1, j].totalCost + deleteCost;
table[i, j] = (Action.Delete, deleteTotalCost, deleteCost);
}
else if (str[i - 1] == str[n - j])
{
table[i, j] = (Action.Unchanged, table[i - 1, j - 1].totalCost, 0);
}
else
{
var insertCost = costFn(str[n - j]);
var deleteCost = costFn(str[i - 1]);
var insertTotalCost = table[i, j - 1].totalCost + insertCost;
var deleteTotalCost = table[i - 1, j].totalCost + deleteCost;
if (insertTotalCost <= deleteTotalCost)
{
table[i, j] = (Action.Insert, insertTotalCost, insertCost);
}
else
{
table[i, j] = (Action.Delete, deleteTotalCost, deleteCost);
}
}
}
}
// The cost is the sum of actionCost for all inserts or all deletes.
// (Both have the same value, because of symmetry.)
int palindromeCost = 0;
for (int i = n, j = n; i > 0 || j > 0;)
{
var (action, totalCost, actionCost) = table[i, j];
switch (action)
{
case Action.Insert:
palindromeCost += actionCost;
j--;
break;
case Action.Delete:
i--;
break;
case Action.Unchanged:
i--;
j--;
break;
}
}
return palindromeCost;
}
void Main()
{
editDistancePalindrime("abc", defaultEditCost).Dump();
// 'abc' -> 'c' or 'abcba' (cost 3)
editDistancePalindrime("anchitjain", defaultEditCost).Dump();
// 'anchitjain' -> 'nitin' or 'anchiajtjaihcna' (cost 23)
}