0

I came from this question but there are also possible invalid values within my string. e.g.

string input = "1;2;3;4;5;6x;7;8;9;1x0";

should result into [1,2,3,4,5,7,8,9], because ["6x", "1x0"] are invalid integer values

My approach: https://dotnetfiddle.net/Ji4bzq

string i = "1;2;3;4;5;6x;7;8;9;1x0";
int temp = -1;
int[] r = i.Split(';').Where(x => int.TryParse(x, out  temp)).Select(_ => temp).ToArray();

which seems to work but feels kinda wrong because of that Select(_ => temp) part.

Question: Is there a better way in terms of readability and reliability? (AsParallel should fail here)

stjns
  • 1,420
  • 13
  • 16
Impostor
  • 2,080
  • 22
  • 43

5 Answers5

4

If using C# 7.0 then you can use the var out feature:

var result = input.Split(';').Select(s => (int.TryParse(s, out int v), v))
                  .Where(pair => pair.Item1)
                  .Select(pair => pair.v);

Don't know if this is more or less readable but I personally prefer avoiding these side effected temp variables. As for the comment about it being longer and perhaps just having int.TryParse(s, out int v) ? v : null - then this will result in a collection of Nullable<int> instead of integers so depends what you prefer.

Community
  • 1
  • 1
Gilad Green
  • 36,708
  • 7
  • 61
  • 95
  • Or `out int v`? – Trevor Mar 06 '20 at 13:54
  • 1
    also `Split(";")` isn't correct... :) Type-o perhaps `'`? – Trevor Mar 06 '20 at 13:56
  • 1
    @Çöđěxěŕ - corrected. Got too used to python – Gilad Green Mar 06 '20 at 13:56
  • Isn't there a more readable appraoch e.g. `.Select(s => (int.TryParse(s, out int v) ? v : null)` I mean you turned 105 into 125 characters code – Impostor Mar 06 '20 at 13:57
  • 4
    I think it is not more readable :) – Selim Yildiz Mar 06 '20 at 13:57
  • @Dr.Snail - you could but then it won't return a collection of ints but of nullable ints – Gilad Green Mar 06 '20 at 13:58
  • @SelimYıldız - perhaps but I personally prefer avoiding these kind of `temp` variables... and here is a way to do so – Gilad Green Mar 06 '20 at 14:00
  • 1
    @Dr.Snail according to `but feels kinda wrong because of that Select(_ => temp)`, does your solution even work, if so, what's the bottleneck, what tests have been performed to prove otherwise? Also readability concern's, how about the old school loop, that's easy to maintain and understand; linq throws all of this under the hood anyways :) – Trevor Mar 06 '20 at 14:00
  • 4
    @Dr.Snail "I mean you turned 105 into 125 characters code" Aside from the actual answer, shorter code doesn't mean its more readable. Its better to be more expressive, and make it explicitly clear what is happening. – Caramiriel Mar 06 '20 at 14:06
1

The main problem with using TryParse() style methods is, of course, that they use out parameters which don't play nice with Linq.

Because of that we actually use simple helper methods for parsing, which return tuples rather than using out parameters.

For example:

public static class Parse
{
    public static (bool wasSuccessful, int value) TryParse(string text)
    {
        bool success = int.TryParse(text, out var value);
        return (success, value);
    }
}

Then you can use Parse.TryParse rather than the int.TryParse() which IMO often makes the code more readable (although not any shorter).

Using the sample above, you can write your code like this:

var r = input.Split(';')
   .Select(Parse.TryParse)
   .Where(parse => parse.wasSuccessful)
   .Select(parse => parse.value)
   .ToArray();

Or if you prefer Linq Query Syntax (which I sometimes find more readable, but YMMV):

var s = (
    from   item in input.Split(';')
    let    parse = Parse.TryParse(item)
    where  parse.wasSuccessful
    select parse.value
).ToArray();

(Although I do feel that the ToArray() at the end there is a bit ugly...)

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
0

Here's an alternate solution using Regex.

The pattern ((?<=;)|^)[0-9]*((?=;)|$) matches numbers that are surrounded by ;;

Try it out

string i = "1;2;3;4;5;6x;7;8;9;1x0";
string pattern = "((?<=;)|^)[0-9]*((?=;)|$)";
int[] r = Regex.Matches(i, pattern).Cast<Match>().Select(x => int.Parse(x.Value)).ToArray();
Innat3
  • 3,561
  • 2
  • 11
  • 29
0

Here is an alternate version using nullables:

string i = "1;2;3;4;5;6x;7;8;9;1x0";
int[] r = i.Split(';')
   .Select(s => int.TryParse(s, out int v) ? (int?) v : null)
   .Where(j => j.HasValue) 
   .Select(j => j.Value)  
   .ToArray();            

Which is more readable is a matter of personal taste. Some would prefer an loop instead of the linq.

Palle Due
  • 5,929
  • 4
  • 17
  • 32
0

A modified & more readable version of Gilad's excellent answer, using ValueTuples:

var integers = input.Split(';')
                    .Select(_ => (Success: int.TryParse(_, out var n), IntegerValue: n))
                    .Where(_ => _.Success)
                    .Select(_ => _.IntegerValue);
Victor Wilson
  • 1,720
  • 1
  • 11
  • 22