Reputation: 397
I have a string (folderpath) and am splitting it by a character (\) which produces and array of substrings (individual folders). How do I get the last but one folder from this, in one line LINQ query.
Basically convert below to a single line LINQ query.
var test = item.Split('\\');
var count = test.Length;
var folder = test[count - 2];
I am getting confused on how to fetch the count of the array and then specify count -2, in a single line.
Upvotes: 1
Views: 2172
Reputation: 7029
Let's go full thermonuclear with this answer just for fun. We can create a single line query using LINQ with performance that is comparable to your solution but we have to create a couple of extension methods first.
The problem with Split
is that it returns a string[]
and what we really need is an IEnumerable<string>
. Yes, all string[]
are IEnumerabl<string>
but not all IEnumerable<string>
are string[]
- what we are really interested in is lazy evaluation and we are not going to get that with Split
. So let's roll our own!
public static class StringExtensions
{
public static IEnumerable<string> SplitAsEnumerable(this string source, char splitter)
{
if (source == null)
{
yield break;
}
var builder = new StringBuilder();
foreach (char c in source)
{
if (c != splitter)
{
builder.Append(c);
}
else
{
if (builder.Length > 0)
{
yield return builder.ToString();
builder.Clear();
}
}
}
}
}
You can go as crazy with this as you want. Add overloads to accept char[]
, string
, and string[]
to split your string. Use your imagination.
Now this is a good first step but we need something that is going to allow us to take the last elements of an IEnumerable
. Interactive Extensions provides a TakeLast
extension method but we don't want no stinkin' dependencies so let's roll our own!
public static class EnumerableExtensions
{
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
{
var queue = new Queue<T>(count);
if (source == null)
{
yield break;
}
foreach (var item in source)
{
if (queue.Count == count)
{
queue.Dequeue();
}
queue.Enqueue(item);
}
foreach (var item in queue)
{
yield return item;
}
}
}
Now we can take as many elements off the end as we want.
The above extension methods look great! I'm sure there are some optimizations that could be made and of course we need to keep an eye out for edge cases but this should be enough for us to get started.
Now we can use our extension methods and LINQ. Here we go:
var folder = item.SplitAsEnumerable('\\').TakeLast(2).FirstOrDefault();
There you go - you have selected the second to last substring with a runtime of O(n) which is comparable to your existing solution and a (potentially) better space complexity since the Queue
we are using may be smaller than the array returned by Split
(this will depend on the count
passed to TakeLast
).
Upvotes: -1
Reputation: 3681
Out of the box, LINQ doesn't have a method to do this, you could write your own extension method to do something similar but looking at the source code for Last()
- which is the closest thing to what you are trying to achieve - it actually does something very similar to what you are doing:
int count = list.Count;
if (count > 0) return list[count - 1];
So there is no magic going on behind the scenes, in my opinion, you should just keep the code that you already have as it is does what it needs to and it is clear and easy to understand rather than creating an unnecessary extension method or complicated LINQ query
Upvotes: 1
Reputation:
I don't see any benefit to using LINQ here, but here goes:
item.Split('\\').Reverse().Skip(1).First();
Due to the reversal, this will perform worse than your existing code.
Upvotes: 5