Dependencies/Pixie/Pixie/NameSuggestion.cs
using System;
using System.Collections.Generic;

namespace WasmBox.Pixie {
    /// <summary>
    /// Contains functionality that suggests names for "Did you mean ...?" messages.
    /// </summary>
    public static class NameSuggestion {
        /// <summary>
        /// Suggests a name from a sequence of names by picking the
        /// name that is the most similar to the spelled name.
        /// </summary>
        /// <param name="spelledName">The name that was spelled.</param>
        /// <param name="possibleNames">
        /// A sequence of valid names to choose from.
        /// </param>
        /// <returns>
        /// The name in <paramref name="possibleNames" /> which is most
        /// similar to the spelled name; <c>null</c> if the sequence of
        /// possible names is empty.
        /// </returns>
        public static string SuggestName(
            string spelledName, IEnumerable<string> possibleNames) {
            string closestName = null;
            int closestNameDistance = int.MaxValue;
            foreach (var name in possibleNames) {
                // TODO: optimize this by taking advantage of lower
                // bound on Levenshtein distance imposed by difference
                // in string length.
                int dist = LevenshteinDistance(spelledName, name);
                if (dist < closestNameDistance) {
                    closestNameDistance = dist;
                    closestName = name;
                }
            }
            return closestName;
        }

        private static int Min(int a, int b, int c) {
            return Math.Min(Math.Min(a, b), c);
        }

        /// <summary>
        /// Computes the Levenshtein distance between two strings.
        /// </summary>
        /// <param name="s">The first string.</param>
        /// <param name="t">The second string.</param>
        /// <returns>The Levenshtein distance between the strings.</returns>
        private static int LevenshteinDistance(string s, string t) {
            // This algorithm is based on the code from Wikipedia
            // (https://en.wikipedia.org/wiki/Levenshtein_distance), which was
            // originally presented in "Fast, memory efficient Levenshtein algorithm"
            // by Sten Hjelmqvist.
            // (https://www.codeproject.com/Articles/13525/Fast-memory-efficient-Levenshtein-algorithm)

            // Handle degenerate cases.
            if (s.Length == 0)
                return t.Length;

            if (t.Length == 0)
                return s.Length;

            var sChars = s.ToCharArray();
            var tChars = t.ToCharArray();

            // Create two work vectors of integer distances.
            var v0 = new int[t.Length + 1];
            var v1 = new int[t.Length + 1];

            // Initialize v0 (the previous row of distances).
            // This row is A[0][i]: edit distance for an empty s.
            // The distance is just the number of characters to delete from t.
            for (int i = 0; i < v0.Length; i++) {
                v0[i] = i;
            }

            for (int i = 0; i < s.Length; i++) {
                // Calculate v1 (current row distances) from the previous row v0.

                // First element of v1 is A[i+1][0]
                // Edit distance is delete (i+1) chars from s to match empty t.
                v1[0] = i + 1;

                // Use formula to fill in the rest of the row.
                for (int j = 0; j < t.Length; j++) {
                    var cost = (sChars[i] == tChars[j]) ? 0 : 1;
                    v1[j + 1] = Min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
                }

                // Copy v1 (current row) to v0 (previous row) for next iteration.
                for (int j = 0; j < v0.Length; j++) {
                    v0[j] = v1[j];
                }
            }

            return v1[t.Length];
        }
    }
}