krillgar
krillgar

Reputation: 12805

How to combine IEnumerable<string[]>

I'm trying to work on improving my LINQ skills, while at the same time beginning with Code Kata. Right now, I'm trying to improve my work on this Kata by using LINQ instead of foreach loops.

The requirements:

Have a method that will split a string on new lines ('\n') into separate strings, and then split each of those on commas to a max of two numbers each.

Here's what I have so far:

private int Add(string numbers) {
    var arrays = numbers.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
    var list = arrays.Select(s => s.Split(new char[] { ',', }, StringSplitOptions.RemoveEmptyEntries));

    // Additional code here...
}

As it stands right now, list is an IEnumerable<string[]>. I have been looking through the various LINQ methods (Join(), Aggregate(), etc), but haven't found anything that returns IEnumerable<string>. I'm starting to think that I'll need to write an extension method for that, but was hoping to see if there was an alternative I was missing.

EDIT

My end goal with this is to wind up with an IEnumerable<int>, though I was thinking I'd have to make a stop at an IEnumerable<string> before that point. If that can be combined into one step, that'd be even better.

Upvotes: 0

Views: 1372

Answers (4)

Erti-Chris Eelmaa
Erti-Chris Eelmaa

Reputation: 26268

Yaay LINQ time! I was just re-reading Jon Skeet Edulinq SelectMany section.

This task is not too good for LINQ because you need control over the code and you have specific cases. Either way I would use some kind of decent tokenizer that's more safer and has better error handling. This is not the case to fire up LINQ for one-liners.

Ps, you can get away with SelectMany statement.

int sum = numbers
    .Split(new[] {"\r\n", "\n"}, StringSplitOptions.None)
    .SelectMany(line => line.Split(new[] {','}), 
                   (line, number) => int.Parse(number))
    .Sum();

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500525

Fortunately it's really simple - you just want SelectMany, which acts as a "flatten" operation:

IEnumerable<string> strings = list.SelectMany(x => x);

Or avoid Select to start with, and go straight to SelectMany:

IEnumerable<string> list = arrays.SelectMany(s => s.Split(new char[] { ',', }, 
                                         StringSplitOptions.RemoveEmptyEntries));

(Obviously you can use var for both of these - I've only included the type for clarity.)

And as Habib has mentioned, you don't even need the intermediate arrays variable:

IEnumerable<string> list =
     numbers.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
            .SelectMany(s => s.Split(new char[] { ',' }, 
                                     StringSplitOptions.RemoveEmptyEntries));

And to get an IEnumerable<int>:

IEnumerable<int> list =
     numbers.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
            .SelectMany(s => s.Split(new char[] { ',' }, 
                                     StringSplitOptions.RemoveEmptyEntries))
            .Select(s => int.Parse(s));

This will throw an exception (lazily) if any of the strings can't be parsed as an integer.

Upvotes: 7

Habib
Habib

Reputation: 223257

You need SelectMany, you can do that in one statement like:

IEnumerable<string> result = 
    numbers.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
           .Select(r=> r.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
           .SelectMany(r=>r);

If you need parsed int values then you can use int.TryParse on each string value like:

string numbers = "1,2,3\n4,5,6\n7,8,9";
int temp;
IEnumerable<int> parsedNumbers =
                numbers.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(r => r.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                .SelectMany(r => r)
                .Select(r => { return int.TryParse(r, out temp) ? temp : int.MinValue; });

int.TryParse would fail for invalid values and in that particular case you can return any value indicating an error like int.MinValue

EDIT:

You can also do:

string numbers = "1,2,3\n4,5,6\n7,8,9";
IEnumerable<int> parsedNumbers =
    numbers.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
           .SelectMany(r => r.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
           .Select(int.Parse);

That will throw an exception in case of any invalid value.

Upvotes: 2

samjudson
samjudson

Reputation: 56853

Rather than use arrays.Select() use arrays.SelectMany() which should return you an string array, rather than an enumeration of string arrays.

Upvotes: 1

Related Questions