Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Linq/Lambda expression: How to select an integer out of a string?

I think the best way to explain my question is with a short (generic) linq-to-objects code sample:

IEnumerable<string> ReadLines(string filename)
{
    string line;
    using (var rdr = new StreamReader(filename))
        while ( (line = rdr.ReadLine()) != null)
           yield return line;
}

IEnumerable<int> XValuesFromFile(string filename)
{
    return ReadLines(filename)
               .Select(l => l.Substring(3,3))
               .Where(l => int.TryParse(l))
               .Select(i => int.Parse(i));
}

Notice that this code parses the integer twice. I know I'm missing an obvious simple way to eliminate one of those calls safely (namely because I've done it before). I just can't find it right now. How can I do this?

like image 297
Joel Coehoorn Avatar asked Dec 08 '22 04:12

Joel Coehoorn


2 Answers

How about:

int? TryParse(string s)
{
    int i;
    return int.TryParse(s, out i) ? (int?)i : (int?)null;
}
IEnumerable<int> XValuesFromFile(string filename)
{
    return from line in ReadLines(filename)
           let start = line.Substring(3,3)
           let parsed = TryParse(start)
           where parsed != null
           select parsed.GetValueOrDefault();
}

You could probably combine the second/third lines if you like:

    return from line in ReadLines(filename)
           let parsed = TryParse(line.Substring(3,3))

The choice of GetValueOrDefault is because this skips the validation check that casting (int) or .Value perform - i.e. it is (ever-so-slightly) faster (and we've already checked that it isn't null).

like image 58
Marc Gravell Avatar answered Dec 10 '22 01:12

Marc Gravell


It's not exactly pretty, but you can do:

return ReadLines(filename)
    .Select(l =>
                {
                    string tmp = l.Substring(3, 3);
                    int result;
                    bool success = int.TryParse(tmp, out result);
                    return new
                               {
                                   Success = success,
                                   Value = result
                               };
                })
    .Where(i => i.Success)
    .Select(i => i.Value);

Granted, this is mostly just pushing the work into the lambda, but it does provide the correct answers, with a single parse (but extra memory allocations).

like image 42
Reed Copsey Avatar answered Dec 10 '22 01:12

Reed Copsey