The Algorithms logo
The Algorithms
AboutDonate

NYSIIS

using System.Globalization;
using System.Linq;
using System.Text;

namespace Algorithms.Encoders;

/// <summary>
///     Class for NYSIIS encoding strings.
/// </summary>
public class NysiisEncoder
{
    private static readonly char[] Vowels = { 'A', 'E', 'I', 'O', 'U' };

    /// <summary>
    ///     Encodes a string using the NYSIIS Algorithm.
    /// </summary>
    /// <param name="text">The string to encode.</param>
    /// <returns>The NYSIIS encoded string (all uppercase).</returns>
    public string Encode(string text)
    {
        text = text.ToUpper(CultureInfo.CurrentCulture);
        text = TrimSpaces(text);
        text = StartReplace(text);
        text = EndReplace(text);

        for (var i = 1; i < text.Length; i++)
        {
            text = ReplaceStep(text, i);
        }

        text = RemoveDuplicates(text);
        return TrimEnd(text);
    }

    private string TrimSpaces(string text) => text.Replace(" ", string.Empty);

    private string RemoveDuplicates(string text)
    {
        var sb = new StringBuilder();
        sb.Append(text[0]);
        foreach (var c in text)
        {
            if (sb[^1] != c)
            {
                sb.Append(c);
            }
        }

        return sb.ToString();
    }

    private string TrimEnd(string text)
    {
        var checks = new (string from, string to)?[]
        {
            ("S", string.Empty),
            ("AY", "Y"),
            ("A", string.Empty),
        };
        var replacement = checks.FirstOrDefault(t => text.EndsWith(t!.Value.from));
        if (replacement is { })
        {
            var (from, to) = replacement!.Value;
            text = Replace(text, text.Length - from.Length, from.Length, to);
        }

        return text;
    }

    private string ReplaceStep(string text, int i)
    {
        (string from, string to)[] replacements =
        {
            ("EV", "AF"),
            ("E", "A"),
            ("I", "A"),
            ("O", "A"),
            ("U", "A"),
            ("Q", "G"),
            ("Z", "S"),
            ("M", "N"),
            ("KN", "NN"),
            ("K", "C"),
            ("SCH", "SSS"),
            ("PH", "FF"),
        };
        var replaced = TryReplace(text, i, replacements, out text);
        if (replaced)
        {
            return text;
        }

        // H[vowel] or [vowel]H -> text[i-1]
        if (text[i] == 'H')
        {
            if (!Vowels.Contains(text[i - 1]))
            {
                return ReplaceWithPrevious();
            }

            if (i < text.Length - 1 && !Vowels.Contains(text[i + 1]))
            {
                return ReplaceWithPrevious();
            }
        }

        // [vowel]W -> [vowel]
        if (text[i] == 'W' && Vowels.Contains(text[i - 1]))
        {
            return ReplaceWithPrevious();
        }

        return text;

        string ReplaceWithPrevious() => Replace(text, i, 1, text[i - 1].ToString());
    }

    private bool TryReplace(string text, int index, (string, string)[] opts, out string result)
    {
        for (var i = 0; i < opts.Length; i++)
        {
            var check = opts[i].Item1;
            var repl = opts[i].Item2;
            if (text.Length >= index + check.Length && text.Substring(index, check.Length) == check)
            {
                result = Replace(text, index, check.Length, repl);
                return true;
            }
        }

        result = text;
        return false;
    }

    private string StartReplace(string start)
    {
        var checks = new (string from, string to)?[]
        {
            ("MAC", "MCC"),
            ("KN", "NN"),
            ("K", "C"),
            ("PH", "FF"),
            ("PF", "FF"),
            ("SCH", "SSS"),
        };
        var replacement = checks.FirstOrDefault(t => start.StartsWith(t!.Value.from));
        if (replacement is { })
        {
            var (from, to) = replacement!.Value;
            start = Replace(start, 0, from.Length, to);
        }

        return start;
    }

    private string EndReplace(string end)
    {
        var checks = new (string from, string to)?[]
        {
            ("EE", "Y"),
            ("IE", "Y"),
            ("DT", "D"),
            ("RT", "D"),
            ("NT", "D"),
            ("ND", "D"),
        };
        var replacement = checks.FirstOrDefault(t => end.EndsWith(t!.Value.from));
        if (replacement is { })
        {
            var (from, to) = replacement!.Value;
            end = Replace(end, end.Length - from.Length, from.Length, to);
        }

        return end;
    }

    private string Replace(string text, int index, int length, string substitute) =>
        text[..index] + substitute + text[(index + length) ..];
}