Reputation: 23
I found this piece of code on stackoverflow some time ago, but can't seem to find it again. All credit to the author. Sorry I could not link.
QUESTION? Can some C# LINQ guru please break down this statement step by step as I am having difficulty understanding it. It certainly works well and does the job, but how?
Line to Split
var line = $"13351.750815 26646.150876 6208.767863 26646.150876 1219.200000 914.400000 0.000000 1 \"Beam 1\" 0 1 1 1 0 1 1e8f59dd-142d-4a4d-81ff-f60f93f674b3";
var splitLineResult = line.Trim().Split('"')
.Select((element, index) => index % 2 == 0
? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
: new string[] { element })
.SelectMany(element => element).ToList();
Result of Statement in LinqPad
Upvotes: 2
Views: 255
Reputation: 18155
You need to begin by analysing the input you have in hand.
13351.750815 26646.150876 6208.767863 26646.150876 1219.200000 914.400000 0.000000 1 "Beam 1" 0 1 1 1 0 1 1e8f59dd-142d-4a4d-81ff-f60f93f674b3
The input consists of few alphanumeric strings separated by Whitespace. However, there is one special case that needs to be handled as well. The word "Beam 1
" is enclosed in Quotes.
Now, let's break down the Linq statement.
line.Trim().Split('"')
The first statement splits the input based on the delimiter Quotes. This splits the string into 3 parts.
As you can observe the first(in 0th Index) and Last(in index position 2) needs to be split further while, the element in the in index position 1 has already been parsed. This is where the second part of Linq statement comes into picture.
.Select((element, index) => index % 2 == 0
? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
: new string[] { element })
In the above statement the Select((element, index) => index % 2 == 0
part checks if the current index position is in an even position. If so, it needs to be split the substring further based on delimiter ' ' (whitespace)
. Otherwise, it creates an array with single entity 'Beam 1'
At the end of the second part, what you get is a collection 3 sub-collections of alphanumeric strings (IEnumerble<string[]>
).
What now needs to be done is create a collection by flattening the parent collection. This done using Enumerable.SelectMany
.
.SelectMany(element => element).ToList();
Hope that helped in understanding the Linq query better
Upvotes: 8
Reputation: 6489
Seems like there are some really good answers here already, but I had already almost finished mine when I saw people had beat me to it, so here's my version. Hope it brings something to the table.
My approach was writing it out in non Linq code with comments
var line = $"13351.750815 26646.150876 6208.767863 26646.150876 1219.200000 914.400000 0.000000 1 \"Beam 1\" 0 1 1 1 0 1 1e8f59dd-142d-4a4d-81ff-f60f93f674b3";
line = line.Trim(); //remove leading and trailing white space
var tempArray1 = line.Split('"'); //split the line to a string array on "
//so the string array we get, is everything before, between, and after "
//.Select((element, index)
//Basically doing a for-loop on the array we got
List<string[]> tempListForElements = new List<string[]>(); //initialize a temporary list of string arrays we're going to be using
for (var index = 0; index < tempArray1.Length; index++)
{
var element = tempArray1[index];
//now we're getting to the ternary, which is basically like an inline if-else statement
//index % 2 == 0 ? <if true> : <if false>
//if remainder of division by 2 is 0, so basically a way of doing two
//different things for every other iterator of the loop
if (index % 2 == 0)
{
//if on the first or last iteraton on the loop (before and after " in the line)
tempListForElements.Add(element.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)); //we create yet another string array splitting on each whitespace and add it to our temporary list
}
else
{
//if on the second iteraton on the loop (between ")
tempListForElements.Add(new string[] { element }); //we're creating yet another string array, this time there's just one element in the array though, and then add it to our temporary list
}
}
//.SelectMany(element => element).ToList()
//we're basically turning out list of 3 string array into one string array
//can't be asked to type it out in non linq since just realized there are some good answers here already,
//but imagine initializing a string array with the correct size and then a foreach loop adding each string to it in order.
Upvotes: 2
Reputation: 11364
Breaking up the statement
var splitLineResult = line.Trim().Split('"')
.Select((element, index) => index % 2 == 0
? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
: new string[] { element }).ToList()
.SelectMany(element => element).ToList();
Trim()
: Removes any spaces in the beginning or the end.
Split('"')
: Split the string into an array of string with double quote as a delimiter
Select()
: Only select a specific part from the element being iterated. In this scenario, its the element and its index in the entire string (starts with 0).
Within your select statement, you have %2 ==0
.. which is true only for every other element. (skips 1)
You can do an if else statement with ?
and :
a. If true print 1 else print 0
can be thought of as true ? print 1 : print 0
SelectMany()
: Selects all the elements that are returned. Instead of returning 3 arrays, it returns the elements from each of the array.
ToList()
: Converts the array to a list.
Upvotes: 2
Reputation: 1182
if you convert this Linq in normal for loop it will look like this.
var line = $"13351.750815 26646.150876 6208.767863 26646.150876 1219.200000 914.400000 0.000000 1 \"Beam 1\" 0 1 1 1 0 1 1e8f59dd-142d-4a4d-81ff-f60f93f674b3";
string[] splitLineResult = line.Trim().Split('"');
var list = new List<string[]>();
for (int i = 0; i < splitLineResult.Length; i++)
{
if (i % 2 == 0)
{
list.Add(splitLineResult[i].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
}
else
{
list.Add(new string[] { splitLineResult[i] });
}
}
var finalList = list.SelectMany(x=>x).ToList();
and for SelectMany method, you can refer MS documentation https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.selectmany?view=netframework-4.8
Upvotes: 1