关于c#:从带有尾随垃圾的字符串中解析整数

Parse an integer from a string with trailing garbage

我需要解析一个出现在字符串开头的十进制整数。

小数后面可能有尾随的垃圾。这需要被忽略(即使它包含其他数字)。

例如

1
2
3
4
"1" => 1
" 42" => 42
" 3 -.X.-" => 3
" 2 3 4 5" => 2

.NET框架中是否有内置方法来执行此操作?

int.TryParse()不适用。它允许使用尾随空格,但不允许使用其他尾随字符。

实现这一点很容易,但是如果标准方法存在的话,我更愿意使用它。


您可以使用LINQ来执行此操作,不需要正则表达式:

1
2
3
4
public static int GetLeadingInt(string input)
{
   return Int32.Parse(new string(input.Trim().TakeWhile(c => char.IsDigit(c) || c == '.').ToArray()));
}

这适用于您提供的所有示例:

1
2
3
4
5
6
7
8
9
10
11
string[] tests = new string[] {
  "1",
  " 42",
  " 3 -.X.-",
  " 2 3 4 5"
};

foreach (string test in tests)
{
   Console.WriteLine("Result:" + GetLeadingInt(test));
}


1
2
3
4
foreach (var m in Regex.Matches(" 3 - .x. 4", @"\d+"))
{
    Console.WriteLine(m);
}

按评论更新

不知道你为什么不喜欢正则表达式,所以我只会发布我认为最短的解决方案。

要获取第一个int:

1
2
3
Match match = Regex.Match(" 3 - .x. - 4", @"\d+");
if (match.Success)
    Console.WriteLine(int.Parse(match.Value));


没有标准的.NET方法可以做到这一点,尽管我不会惊讶地发现vb在microsoft.visualBasic程序集中有一些东西(与.NET一起提供,因此即使从C使用它也不是问题)。

结果是否总是非负的(这会使事情变得更容易)?

说实话,正则表达式是这里最简单的选择,但是…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static string RemoveCruftFromNumber(string text)
{
    int end = 0;

    // First move past leading spaces
    while (end < text.Length && text[end] == ' ')
    {
        end++;
    }

    // Now move past digits
    while (end < text.Length && char.IsDigit(text[end]))
    {
        end++;
    }

    return text.Substring(0, end);
}

然后,您只需要根据RemoveCruftFromNumber的结果调用int.TryParse(不要忘记整数可能太大,无法存储在int中)。


我喜欢吃甜甜圈。

不过,我想补充一点,char.IsDigitchar.IsNumber还允许使用一些Unicode字符,这些字符是其他语言和脚本中的数字(请参见此处)。如果只想检查数字0到9,可以使用"0123456789".Contains(c)

三个示例实现:

要删除尾随的非数字字符:

1
2
3
var digits = new string(input.Trim().TakeWhile(c =>
    ("0123456789").Contains(c)
).ToArray());

要删除前导非数字字符:

1
2
3
var digits = new string(input.Trim().SkipWhile(c =>
    !("0123456789").Contains(c)
).ToArray());

要删除所有非数字字符:

1
2
3
var digits = new string(input.Trim().Where(c =>
    ("0123456789").Contains(c)
).ToArray());

当然还有:int.Parse(digits)int.TryParse(digits, out output)


这并不能真正回答您的问题(关于内置C方法),但您可以尝试逐个切掉输入字符串末尾的字符,直到int.TryParse()接受它作为有效数字:

1
2
3
4
5
6
7
for (int p = input.Length;  p > 0;  p--)
{
    int  num;
    if (int.TryParse(input.Substring(0, p), out num))
        return num;
}
throw new Exception("Malformed integer:" + input);

当然,如果input很长的话,这会很慢。

附录(2016年3月)

这可以通过在尝试每次分析之前切掉右侧所有非数字/非空格字符来更快地实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int p = input.Length;  p > 0;  p--)
{
    char  ch;
    do
    {
        ch = input[--p];
    } while ((ch < '0'  ||  ch > '9')  &&  ch != ' '  &&  p > 0);
    p++;

    int  num;
    if (int.TryParse(input.Substring(0, p), out num))
        return num;
}
throw new Exception("Malformed integer:" + input);

这就是我在Java中所做的事情:

1
2
3
4
5
6
int parseLeadingInt(String input)
{
    NumberFormat fmt = NumberFormat.getIntegerInstance();
    fmt.setGroupingUsed(false);
    return fmt.parse(input, new ParsePosition(0)).intValue();
}

我希望在.NET中也能实现类似的功能。

这是我当前使用的基于regex的解决方案:

1
2
3
4
5
6
7
8
9
10
int? parseLeadingInt(string input)
{
    int result = 0;
    Match match = Regex.Match(input,"^[ \t]*\\d+");
    if (match.Success && int.TryParse(match.Value, out result))
    {
        return result;
    }
    return null;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
string s =" 3 -.X.-".Trim();
string collectedNumber = string.empty;
int i;

for (x = 0; x < s.length; x++)
{

  if (int.TryParse(s[x], out i))
     collectedNumber += s[x];
  else
     break;     // not a number - that's it - get out.

}

if (int.TryParse(collectedNumber, out i))
    Console.WriteLine(i);
else
    Console.WriteLine("no number found");


也可以加我的。

1
2
3
4
5
6
7
8
9
10
11
12
13
        string temp =" 3 .x£";
        string numbersOnly = String.Empty;
        int tempInt;
        for (int i = 0; i < temp.Length; i++)
        {
            if (Int32.TryParse(Convert.ToString(temp[i]), out tempInt))
            {
                numbersOnly += temp[i];
            }
        }

        Int32.TryParse(numbersOnly, out tempInt);
        MessageBox.Show(tempInt.ToString());

消息框只是为了测试的目的,只要验证方法是否有效,就删除它。


我不知道你为什么会在这种情况下避免Regex。

这里有一个小黑客,你可以适应你的需要。

"3-.x.-".tochararray().findinteger().tolist().foreach(console.writeline);

1
2
3
4
5
6
7
8
9
10
11
public static class CharArrayExtensions
{
    public static IEnumerable<char> FindInteger(this IEnumerable<char> array)
    {
        foreach (var c in array)
        {
            if(char.IsNumber(c))
                yield return c;
        }
    }
}

编辑:对于不正确的结果(以及维护开发人员:)是这样的。

修订如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    public static int FindFirstInteger(this IEnumerable<char> array)
    {
        bool foundInteger = false;
        var ints = new List<char>();

        foreach (var c in array)
        {
            if(char.IsNumber(c))
            {
                foundInteger = true;
                ints.Add(c);
            }
            else
            {
                if(foundInteger)
                {
                    break;
                }
            }
        }

        string s = string.Empty;
        ints.ForEach(i => s += i.ToString());
        return int.Parse(s);
    }


1
2
3
4
5
6
7
8
9
    private string GetInt(string s)
    {
        int i = 0;

        s = s.Trim();
        while (i<s.Length && char.IsDigit(s[i])) i++;

        return s.Substring(0, i);
    }