tags:

views:

334

answers:

8

I need to parse a decimal integer that appears at the start of a string.

There may be trailing garbage following the decimal number. This needs to be ignored (even if it contains other numbers.)

e.g.

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

Is there a built-in method in the .NET framework to do this?

int.TryParse() is not suitable. It allows trailing spaces but not other trailing characters.

It would be quite easy to implement this but I would prefer to use the standard method if it exists.

A: 
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");
AngryHacker
That will only parse one digit. The number may have multiple digits.
finnw
@finnw- then just throw another if statement inside the first one to iterate to the following position to check
TStamper
@finnw Ok, here is another iteration that handles multiple numbers
AngryHacker
+5  A: 
foreach (var m in Regex.Matches(" 3 - .x. 4", @"\d+"))
{
    Console.WriteLine(m);
}

Updated per comments

Not sure why you don't like regular expressions, so I'll just post what I think is the shortest solution.

To get first int:

Match match = Regex.Match(" 3 - .x. - 4", @"\d+");
if (match.Success)
    Console.WriteLine(int.Parse(match.Value));
Yuriy Faktorovich
I only need the first number, so you could stick a 'break' in there.
finnw
@finnw: Was confused by the comment you made on another answer. To get the first value use the Regex.Match function, it can be seen in one of my Rollbacks.
Yuriy Faktorovich
@Yuriy, I was referring to multi-digit numbers (e.g. "42"), not multiple numbers in the string.
finnw
+3  A: 

There's no standard .NET method for doing this - although I wouldn't be surprised to find that VB had something in the Microsoft.VisualBasic assembly (which is shipped with .NET, so it's not an issue to use it even from C#).

Will the result always be non-negative (which would make things easier)?

To be honest, regular expressions are the easiest option here, but...

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);
}

Then you just need to call int.TryParse on the result of RemoveCruftFromNumber (don't forget that the integer may be too big to store in an int).

Jon Skeet
The garbage is at the end of the string, not the start (I do not consider the leading space to be garbage, since the built-in functions like int.Parse can handle that.)
finnw
Okay, edited. (Was this the reason for the downvote? If not, I'd be interested to hear what it was for...)
Jon Skeet
"Was this the reason for the downvote? If not, I'd be interested to hear what it was for..." it's like Federer bitching about the ref telling him to be quiet.
Yuriy Faktorovich
Thanks for pointing out that there is no built-in method. Failing that, yes a regular expression is probably the best option. And this answer could be simplified to a regular expression.
finnw
@Yuriy: I'm afraid I don't understand your comment. I always like to hear why I'm being downvoted, so that I can improve my answer. @finnw: Yes, this answer could very easily be simplified to a regex - I didn't do so based on your expression of dislike for regexes in the question :) Let me know if you want me to put that in the answer.
Jon Skeet
@Jon Skeet: The point being that most of us get downmodded all the time and don't start asking questions, be it right or wrong.
Yuriy Faktorovich
@Yuriy: Well next time you're downvoted and you don't know why - ask! Personally I don't downvote an answer without leaving a comment, unless someone has already given the same comment. I see nothing wrong with asking for a reason.
Jon Skeet
@Jon Skeet, actually I had a more fun experience recently, Andrew-Hare downmodded me(pretty sure) and left an incorrect comment. I answered, whereupon he deleted his answer and comments, but not my downmod.
Yuriy Faktorovich
Unless you edited your question, he may well have been unable to remove the downvote. The system is unfortunate that way sometimes.
Jon Skeet
A: 

I'm not sure why you would avoid Regex in this situation.

Here's a little hackery that you can adjust to your needs.

" 3 -.X.-".ToCharArray().FindInteger().ToList().ForEach(Console.WriteLine);

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;
        }
    }
}

EDIT: That's true about the incorrect result (and the maintenance dev :) ).

Here's a revision:

    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);
    }
Chris Martin
That's pretty clever. Of course the maintenance dev will hate you.
AngryHacker
That would give an incorrect result for numbers longer than 1 digit.
finnw
A: 
    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);
    }
najmeddine
@downvoter: could you explain please? is this not working?
najmeddine
I am not the downvoter, but I would guess it's because you do a linear search of the 'nums' list instead of the simpler 'char.IsNumber(s[i])'.
finnw
I guessed that also, but I wasn't aware it existed... anyway glad I learned something and took -1 in the figure ;)
najmeddine
A: 

Might as well add mine too.

        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());

The message box is just for testing purposes, just delete it once you verify the method is working.

Spidey
+6  A: 

You can use Linq to do this, no Regular Expressions needed:

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

This works for all your provided examples:

string[] tests = new string[] {
   "1",
   " 42 ",
   " 3 -.X.-",
   " 2 3 4 5"
};

foreach (string test in tests)
{
   Console.WriteLine("Result: " + GetLeadingInt(test));
}
Donut
Why are you calling ToCharArray? String already implements `IEnumerable<char>`.
Jon Skeet
Whoops. Thanks for that, edited.
Donut
I love it! Thanks for the nice solution.
Chris Martin
A: 

This is how I would have done it in Java:

int parseLeadingInt(String input)
{
    NumberFormat fmt = NumberFormat.getIntegerInstance();
    fmt.setGroupingUsed(false);
    return fmt.parse(input, new ParsePosition(0)).intValue();
}

I was hoping something similar would be possible in .NET.

This is the regex-based solution I am currently using:

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;
}
finnw