根据前缀将整数字符串解析为十进制,八进制或十六进制

时间:2017-09-02 10:45:57

标签: c# .net parsing string-parsing

我想解析一个包含十进制,八进制或十六进制格式的数字的字符串,我想用零堆分配来实现。因此正则表达式和子串出局。

基数由前缀确定:

  • 1234是十进制
  • 由于前导零(十进制668),
  • 01234是八进制 由于领先0x1234(十进制4660)
  • 0x为十六进制

十六进制值可能以0x0X0h为前缀。 A - F个字符可能是大写或小写。

该方法应该有签名:

bool TryParse(string s, out int i)

该方法一旦到达s的末尾,或者一旦到达空格,就应该停止解析。也就是说,以下所有内容应该产生相同的整数值1234:

"1234"
"1234  "
"0X4D2"
"0h4D2"
"0x4d2"
"0x4D2  "
"02322"
"02322  "

我打算自己开始编码,但是想把它放在那里,以防有人可以分享现有的解决方案。如果没有,我会在完成后分享我的解决方案。

1 个答案:

答案 0 :(得分:2)

我最后编写了一个状态机,在一次传递中解析这些值,没有任何堆分配(据我所知)。

以下是方法:

public static bool TryParse(string s, out int i)
{
    const int starting = 0;
    const int negative = 1;
    const int leadingZero = 2;
    const int dec = 3;
    const int oct = 4;
    const int startingHex = 5;
    const int hex = 6;

    var state = starting;
    var value = 0;
    var isNegative = false;

    // ReSharper disable once ForCanBeConvertedToForeach
    for (var idx = 0; idx < s.Length; idx++)
    {
        var c = s[idx];

        switch (state)
        {
            case starting:
            case negative:
            {
                switch (c)
                {
                    case '0':
                        state = leadingZero;
                        break;
                    case '-':
                        if (state == starting)
                        {
                            isNegative = true;
                            state = negative;
                            break;
                        }
                        i = default(int);
                        return false;
                    default:
                        if (char.IsDigit(c))
                        {
                            value = c - '0';
                            state = dec;
                            break;
                        }
                        if (state == starting && char.IsWhiteSpace(c))
                            break;
                        i = default(int);
                        return false;
                }
                break;
            }
            case leadingZero:
            {
                switch (c)
                {
                    case 'x':
                    case 'X':
                    case 'h':
                        state = startingHex;
                        break;
                    case ' ':
                        i = 0;
                        return true;
                    default:
                        if (char.IsDigit(c))
                        {
                            value = c - '0';
                            state = oct;
                            break;
                        }
                        i = default(int);
                        return false;
                }
                break;
            }
            case dec:
            {
                if (char.IsDigit(c))
                {
                    value *= 10;
                    value += c - '0';
                    continue;
                }
                if (char.IsWhiteSpace(c))
                {
                    i = isNegative ? -value : value;
                    return true;
                }
                i = default(int);
                return false;
            }
            case oct:
            {
                var v = c - '0';
                if (v >= 0 && v < 8)
                {
                    value *= 8;
                    value += v;
                    continue;
                }
                if (char.IsWhiteSpace(c))
                {
                    i = isNegative ? -value : value;
                    return true;
                }
                i = default(int);
                return false;
            }
            case hex:
            case startingHex:
            {
                if (c >= '0' && c <= '9')
                {
                    state = hex;
                    var v = c - '0';
                    value *= 16;
                    value += v;
                    continue;
                }
                var cl = char.ToLower(c);
                if (cl >= 'a' && c <= 'f')
                {
                    state = hex;
                    var v = cl - 'a' + 10;
                    value *= 16;
                    value += v;
                    continue;
                }
                if (state == hex && char.IsWhiteSpace(c))
                {
                    i = isNegative ? -value : value;
                    return true;
                }
                i = default(int);
                return false;
            }
        }
    }

    switch (state)
    {
        case dec:
        case oct:
        case hex:
            i = isNegative ? -value : value;
            return true;
        case leadingZero:
            i = 0;
            return true;
        default:
            i = 0;
            return false;
    }
}

xUnit测试:

[Fact]
public void Parse()
{
    void Test(string s, int expected)
    {
        Assert.True(ParseUtil.TryParse(s, out var actual));
        Assert.Equal(expected, actual);
    }

    void TestFails(string s) => Assert.False(ParseUtil.TryParse(s, out var _));

    Test("1234", 1234);
    Test("1234 ", 1234);
    Test("1234  ", 1234);
    Test("0X4D2", 1234);
    Test("0h4D2", 1234);
    Test("0x4d2", 1234);
    Test("0x4D2  ", 1234);
    Test("02322", 1234);
    Test("02322  ", 1234);
    Test("002322  ", 1234);
    Test("0002322  ", 1234);

    Test("-1234", -1234);
    Test("-1234 ", -1234);
    Test("-1234  ", -1234);
    Test("-0X4D2", -1234);
    Test("-0h4D2", -1234);
    Test("-0x4d2", -1234);
    Test("-0x4D2  ", -1234);
    Test("-02322", -1234);
    Test("-02322  ", -1234);
    Test("-002322  ", -1234);
    Test("-0002322  ", -1234);

    Test(" 1234", 1234);
    Test(" 1234 ", 1234);
    Test("  1234  ", 1234);
    Test(" 0X4D2", 1234);
    Test(" 0h4D2", 1234);
    Test(" 0x4d2", 1234);
    Test(" 0x4D2  ", 1234);
    Test(" 02322", 1234);
    Test(" 02322  ", 1234);
    Test(" 002322  ", 1234);
    Test(" 0002322  ", 1234);

    Test("0", 0);
    Test("00", 0);
    Test("000", 0);
    Test("0x0", 0);
    Test(" 0 ", 0);
    Test(" 00 ", 0);
    Test(" 000 ", 0);
    Test(" 0x0 ", 0);

    TestFails("Hello");
    TestFails("0Hello");
    TestFails("0xx1234");
    TestFails("04D2");
    TestFails("4D2");
    TestFails("098LKJ");
    TestFails("0x");
    TestFails("0x ");
    TestFails(" 0x ");
    TestFails("- 123");
    TestFails("--123");
}

希望它可以帮助其他人。