0

I have this string:

string input = "1,2,3,4,s,6";

Pay attention to the s character.

I just want to convert this string in a List<int> using LINQ. I initially tried in this way:

var myList = new List<int>();
input.Split(',').ToList().ForEach(n =>
    myList.Add(int.TryParse(n, out int num) ? num : -1)
);
lista.RemoveAll(e => e == -1);

But I prefer not have any -1 instead of a no-number characters.

So now I try with this:

var myList = new List<int>();
input.Split(',').ToList()
    .FindAll(n => int.TryParse(n, out int _))
    .ForEach(num => myList.Add(int.Parse(num)));

I prefer this, but is really a shame that the parsing happening two times (TryParse at first and then Parse). But, from what I understand, the out variable in TryParse is useless (or not?).

Have you others suggests (using LINQ)?

Gioce90
  • 554
  • 2
  • 10
  • 31
  • https://stackoverflow.com/questions/1297231/convert-string-to-int-in-one-line-of-code-using-linq/37033140#37033140 – Slai Jun 20 '17 at 23:58

9 Answers9

3
public class ParsesStringsToIntsWithLinq
{
    public IEnumerable<int> Parse(string input)
    {
        var i = 0;
        return (from segment in input.Split(',')
            where int.TryParse(segment, out i) 
            select i);
    }
}

[TestClass]
public class Tests
{
    [TestMethod]
    public void IgnoresNonIntegers()
    {
        var input = "1,2,3,4,s,6";
        var output = new ParsesStringsToIntsWithLinq().Parse(input);
        Assert.IsTrue(output.SequenceEqual(new []{1,2,3,4,6}));
    }
}

It doesn't return a List<int> but I have to draw the line somewhere. You can make a list out of it.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
1

Using a nice extension method

public static IEnumerable<T> AsSingleton<T>(this T source) {
    yield return source;
}

(which you can replace with new[] { n } if preferred)

input.Split(',').SelectMany(s => Int32.TryParse(s, out var n) ? n.AsSingleton()  : Enumerable.Empty<int>()).ToList()
NetMage
  • 26,163
  • 3
  • 34
  • 55
  • 1
    That's a neat solution, don't really like the iterator tho. – Deadzone Jun 20 '17 at 23:33
  • You mean the `AsSingleton` ? As mentioned, you can replace it with `new[] { n }` instead. – NetMage Jun 20 '17 at 23:36
  • 4
    This is a bad overload of the name `Singleton`. Surely there's a better name. – Enigmativity Jun 21 '17 at 00:10
  • 5
    The LINQ method `Enumerable.Repeat(source, 1)` already does this. – Enigmativity Jun 21 '17 at 00:11
  • interesting approach, reminded me of this [YOW! Nights October 2013 Joe Albahari - Programming With Purity](https://www.youtube.com/watch?v=aZCzG2I8Hds&t=26m9s) talk. Creating array is a tiny bit cheaper and faster than creating iterator `.SelectMany(s => int.TryParse(s, out var n) ? new[] { n } : new int[0])` – Slai Jun 21 '17 at 01:25
  • @Enigmativity The name is based on mathematics - see https://stackoverflow.com/a/29566211/2557128 – NetMage Jun 21 '17 at 19:17
  • 1
    @slai Actually using a lot of code to create a custom iterator turns out to be faster than anything else because the compiler optimizes out of boxing: https://stackoverflow.com/a/34038332/2557128. I do wonder if `new int[0]` is better than `Enumerable.Empty()`. – NetMage Jun 21 '17 at 19:18
  • Checking source looks like `Enumerable.Empty` should be much better than `new int[0]` since it returns a static array reference instead of creating a new object each time. – NetMage Jun 21 '17 at 19:57
  • BTW, some (me included) do not like the semantics of `Repeat` once. – NetMage Jun 21 '17 at 21:35
  • @NetMage - Do you mean "repeat once" would issue "two values"? – Enigmativity Jun 21 '17 at 23:45
  • No, just that "repeat one times" seems like a peculiar grammatical construction, even in a programming language. It doesn't represent the intent of the code properly. – NetMage Jun 22 '17 at 00:14
0

I prefer to make a nice helper function:

Func<string, int?> tryParse = s => int.TryParse(s, out int n) ? (int?)n : null;

Then it's a simple matter to parse:

string input = "1,2,3,4,s,6";

List<int> myList =
    input
        .Split(',')
        .Select(s => tryParse(s))
        .Where(n => n.HasValue)
        .Select(n => n.Value)
        .ToList();

That gives:

1 
2 
3 
4 
6 
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
0
int i = 0; 
var myList = (from s in input.Split(',') where int.TryParse(s, out i) select i).ToList();

If the numbers are always single ASCII digits:

var myList = "1,2,3,4,s,6".Select(c => c ^ 48).Where(i => i < 10).ToList();

Few slower RegEx alternatives for fun:

var myList2 = Regex.Split("1,2,3,4,s,6", "[^0-9]+").Select(int.Parse).ToList(); // if the string starts and ends with digits

var myList3 = Regex.Replace("1,2,3,4,s,6", "[^0-9]+", " ").Trim().Split(' ').Select(int.Parse).ToList();

var myList4 = Regex.Matches("1,2,3,4,s,6", "[0-9]+").Cast<Match>().Select(m => int.Parse(m.Value)).ToList();
Slai
  • 22,144
  • 5
  • 45
  • 53
0

Why does it have to be LINQ?

Try:

//Come up a better name...
public static List<int> ConvertToIntListNoLinq(string input)
{
    List<int> output = new List<int>();
    foreach(string s in input.Split(','))
    {
        if(int.TryParse(s, out int result))
        {
            output.Add(result);
        }               
    }
    return output;
}

Fiddle

maccettura
  • 10,514
  • 3
  • 28
  • 35
  • The tests are hardly conclusive as with such low running times in the ms you have a large amount of jitter. To avoid this tests in general should run at least several seconds. If run your fiddle multiple times, I sometimes get that the LINQ version is 10x faster, which really shows the jitter with the way you set your test up. – ckuri Jun 21 '17 at 06:21
0

Here's a generic LINQ extension, which utilizes a delegate. This will allow you to pass in a function returning a bool, while "retaining" the result of the out variable (like int.TryParse).


Usage:

string input = "1,2,3,4,s,6";
List<int> myList = input.Split(',').SelectTry<string, int>(int.TryParse).ToList();

Code:

using System.Collections.Generic;

public static class LINQExtensions
{
    public delegate bool TryFunc<TSource, TResult>(TSource source, out TResult result);

    public static IEnumerable<TResult> SelectTry<TSource, TResult>(
        this IEnumerable<TSource> source, TryFunc<TSource, TResult> selector)
    {
        foreach (TSource item in source)
        {
            TResult result;
            if (selector(item, out result))
            {
                yield return result;
            }
        }
    }
}
budi
  • 6,351
  • 10
  • 55
  • 80
0

I think this is a clean way too. Even though it uses that extra variable, the benefit we get is it is clean and understandable.

string ids = "2,4,2,4,5,s"
const int inValidInt = -99;
var ids = ids.Split(',')
.Select(id =>
{
    int parsedId = int.TryParse(id, out parsedId) ? parsedId : inValidInt;
    return parsedId;
})
.Where(x => x != inValidInt).ToList();
Raj Kumar
  • 1,547
  • 14
  • 19
-1

You can do it like this:

List<int> numbers = input
    .Split(',')
    .Where(t => int.TryParse(t, out int a))
    .Select(int.Parse)
    .ToList();
Deadzone
  • 793
  • 1
  • 11
  • 33
-1
  • You don't need to call .Split(...).ToList() as String[] is already enumerable.
  • You can use multiple statements in a lambda with braces.
  • The FindAll, ForEach and RemoveAll methods are not Linq methods, they're members of List<T>. Their Linq equivalent is Where.

Like so:

List<Int32> numbers = "1,2,3,4,s,6"
    .Split(',')
    .Select( s => { Int32 val; return Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out val ) ? val : -1 } )
    .Where( n => n != -1 )
    .ToList();

You can make it more concise with a helper method:

static Int32 Parse(String s) {
    Int32 ret;
    if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
        return ret;
    }
    return -1;
}

Becomes:

List<Int32> numbers = "1,2,3,4,s,6"
    .Split(',')
    .Select( s => Parse( s ) )
    .Where( n => n != -1 )
    .ToList();

If you don't want to reserve -1 then you can use nullable ints:

static Int32? Parse(String s) {
    Int32 ret;
    if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
        return ret;
    }
    return null;
}

List<Int32> numbers = "1,2,3,4,s,6"
    .Split(',')                     // String to String[]
    .Select( s => Parse( s ) )      // String[] to IEnumerable<Int32?>
    .Where( n => n != null )        // filter out nulls
    .Select( n => n.Value )         // IEnumerable<Int32?> to IEnumerable<Int32>
    .ToList();                      // IEnumerable<Int32> to List<Int32>
Dai
  • 141,631
  • 28
  • 261
  • 374