C# format arbitrarily large BigInteger for endless game

拥有回忆 提交于 2019-12-01 06:21:49

In the world of numerical notation, this is actually a solved problem. That is, you could instead use scientific notation to represent these especially large numbers. Scientific notation is compact, allows arbitrary precision for the mantissa, and readily understandable. Personally, that's the approach I'd take.

For the sake of discussion, let's look at what other alternatives you have…


On the face of it, your request boils down to a straight numeric base value to text conversion. Just as we can convert a numeric value to its textual representation in, for example, base 2, base 10, base 16, etc. we can convert a numeric value to a textual representation using base 26, using just the letters a through z as the digits.

Then your GetProceduralSuffix() method would look something like this:

static string GetProceduralSuffix(int value)
{
    StringBuilder sb = new StringBuilder();

    while (value > 0)
    {
        int digit = value % 26;

        sb.Append((char)('a'+ digit));
        value /= 26;
    }

    if (sb.Length == 0)
    {
        sb.Append('a');
    }

    sb.Reverse();
    return sb.ToString();
}

where the Reverse() extension method is this:

public static void Reverse(this StringBuilder sb)
{
    for (int i = 0, j = sb.Length - 1; i < sb.Length / 2; i++, j--)
    {
        char chT = sb[i];

        sb[i] = sb[j];
        sb[j] = chT;
    }
}

However, there's a slight problem with the above. In base 26 represented this way, the digit a corresponds to 0, and so your suffixes will never start with the letter a, at least not after the first one (that's a special case, just like when using decimal notation we use the digit 0 by itself to represent the value of zero). Instead, for example, you'll get ba after z and baa after zz.

Personally, I think that's fine. It would preclude suffixes like aaaz, but only because the suffix notation system would be logical, predictable, and easily reversed (i.e. given a suffix, it's trivial to figure out what that means numerically).

However, if you insist on a sequence like az, aazz, aaazzz, aaaa… and so on, you can use base 27 instead of 26, with a character other than az for the 0 digit, and precompute the suffixes skipping values that would have a 0 digit as you go, and then index the result. For example:

List<string> _hackedValues = new List<string>();

static void PrecomputeValues()
{
    // 531441 = 27 ^ 4, i.e. the first 5-digit base 27 number.
    // That's a large enough number to ensure that the output
    // include "aaaz", and indeed almost all of the 4-digit
    // base 27 numbers
    for (int i = 0; i < 531441; i++)
    {
        string text = ToBase27AlphaString(i);

        if (!text.Contains('`'))
        {
            _hackedValues.Add(text);
        }
    }
}

static string GetProceduralSuffix(int value)
{
    if (hackedValues.Count == 0)
    {
        PrecomputeValues();
    }

    return _hackedValues[value];
}

static string ToBase27AlphaString(int value)
{
    StringBuilder sb = new StringBuilder();

    while (value > 0)
    {
        int digit = value % 27;

        sb.Append((char)('`'+ digit));
        value /= 27;
    }

    if (sb.Length == 0)
    {
        sb.Append('`');
    }

    sb.Reverse();
    return sb.ToString();
}

Here is a complete program that illustrates both techniques, showing the text for "interesting" input (i.e. where the number of characters in the output changes):

class Program
{
    static void Main(string[] args)
    {
        int[] values = { 0, 25, 26, 675, 676 };

        foreach (int value in values)
        {
            Console.WriteLine("{0}: {1}", value, ToBase26AlphaString(value));
        }

        Console.WriteLine();

        List<Tuple<int, string>> hackedValues = new List<Tuple<int, string>>();

        for (int i = 0; i < 531441; i++)
        {
            string text = ToBase27AlphaString(i);

            if (!text.Contains('`'))
            {
                hackedValues.Add(Tuple.Create(i, text));
            }
        }

        Tuple<int, string> prev = null;

        for (int i = 0; i < hackedValues.Count; i++)
        {
            Tuple<int, string> current = hackedValues[i];

            if (prev == null || prev.Item2.Length != current.Item2.Length)
            {
                if (prev != null)
                {
                    DumpHackedValue(prev, i - 1);
                }
                DumpHackedValue(current, i);
            }

            prev = current;
        }
    }

    private static void DumpHackedValue(Tuple<int, string> hackedValue, int i)
    {
        Console.WriteLine("{0}: {1} (actual value: {2})", i, hackedValue.Item2, hackedValue.Item1);
    }

    static string ToBase26AlphaString(int value)
    {
        return ToBaseNAlphaString(value, 'a', 26);
    }

    static string ToBase27AlphaString(int value)
    {
        return ToBaseNAlphaString(value, '`', 27);
    }

    static string ToBaseNAlphaString(int value, char baseChar, int numericBase)
    {
        StringBuilder sb = new StringBuilder();

        while (value > 0)
        {
            int digit = value % numericBase;

            sb.Append((char)(baseChar + digit));
            value /= numericBase;
        }

        if (sb.Length == 0)
        {
            sb.Append(baseChar);
        }

        sb.Reverse();
        return sb.ToString();
    }
}

static class Extensions
{
    public static void Reverse(this StringBuilder sb)
    {
        for (int i = 0, j = sb.Length - 1; i < sb.Length / 2; i++, j--)
        {
            char chT = sb[i];

            sb[i] = sb[j];
            sb[j] = chT;
        }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!