Reputation: 12805
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
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
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
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
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