Reputation: 5188
I am a LINQ newbie trying to use it to acheive the following:
I have a list of ints:-
List<int> intList = new List<int>(new int[]{1,2,3,3,2,1});
Now, I want to compare the sum of the first three elements [index range 0-2] with the last three [index range 3-5] using LINQ. I tried the LINQ Select and Take extension methods as well as the SelectMany method, but I cannot figure out how to say something like
(from p in intList
where p in Take contiguous elements of intList from index x to x+n
select p).sum()
I looked at the Contains extension method too, but that doesn't see to get me what I want. Any suggestions? Thanks.
Upvotes: 75
Views: 83647
Reputation: 1447
Starting from .NET 6 it is possible to use range syntax for Take method.
List<int> intList = new List<int>(new int[]{1, 2, 3, 3, 2, 1});
// Starting from index 0 (including) to index 3 (excluding) will select indexes (0, 1, 2)
Console.WriteLine(intList.Take(0..3).Sum()); // {1, 2, 3} -> 6
// By default is first index 0 and can be used following shortcut.
Console.WriteLine(intList.Take(..3).Sum()); // {1, 2, 3} -> 6
// Starting from index 3 (including) to index 6 (excluding) will select indexes (3, 4, 5)
Console.WriteLine(intList.Take(3..6).Sum()); // {3, 2, 1} -> 6
// By default is last index lent -1 and can be used following shortcut.
Console.WriteLine(intList.Take(3..).Sum()); // {3, 4, 5} -> 6
// Reverse index syntax can be used. Take last 3 items.
Console.WriteLine(intList.Take(^3..).Sum()); // {3, 2, 1} -> 6
// No exception will be raised in case of range is exceeded.
Console.WriteLine(intList.Take(^100..1000).Sum());
So simply put, intList.Take(..3).Sum()
and intList.Take(3..).Sum()
can be used with .NET 6.
Upvotes: 5
Reputation: 1988
To filter by specific indexes (not from-to):
public static class ListExtensions
{
public static IEnumerable<TSource> ByIndexes<TSource>(this IList<TSource> source, params int[] indexes)
{
if (indexes == null || indexes.Length == 0)
{
foreach (var item in source)
{
yield return item;
}
}
else
{
foreach (var i in indexes)
{
if (i >= 0 && i < source.Count)
yield return source[i];
}
}
}
}
For example:
string[] list = {"a1", "b2", "c3", "d4", "e5", "f6", "g7", "h8", "i9"};
var filtered = list.ByIndexes(5, 8, 100, 3, 2); // = {"f6", "i9", "d4", "c3"};
Upvotes: 1
Reputation: 13996
For larger lists, a separate extension method could be more appropriate for performance. I know this isn't necessary for the initial case, but the Linq (to objects) implementation relies on iterating the list, so for large lists this could be (pointlessly) expensive. A simple extension method to achieve this could be:
public static IEnumerable<TSource> IndexRange<TSource>(
this IList<TSource> source,
int fromIndex,
int toIndex)
{
int currIndex = fromIndex;
while (currIndex <= toIndex)
{
yield return source[currIndex];
currIndex++;
}
}
Upvotes: 18
Reputation: 17062
Use Skip then Take.
yourEnumerable.Skip(4).Take(3).Select( x=>x )
(from p in intList.Skip(x).Take(n) select p).sum()
Upvotes: 109