Convert a string in a List using LINQ (cleane

2019-06-05 10:47发布

问题:

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)?

回答1:

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.



回答2:

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


回答3:

You can do it like this:

List<int> numbers = input
    .Split(',')
    .Where(t => int.TryParse(t, out int a))
    .Select(int.Parse)
    .ToList();


回答4:

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 


回答5:

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


回答6:

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



回答7:

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


回答8:

  • 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>