Reputation: 443
This should be very simple but I searched and couldn't find a similar linq example.
I have a function below.
viewModel.SLGDeptSalesList = viewModel.SLGDeptSalesList.Where(
(value, index) => index == 0).ToList();
It returns an index within the sales list.
I need to customize it to return multiple hard coded index's such as 1,3,4,6.
How can I use linq to do this in one line?
Thanks a bunch!
Upvotes: 3
Views: 2201
Reputation: 43812
There is already the built-in LINQ method ElementAt
, so we could also have an ElementsAt
that accepts multiple indices.
public static IEnumerable<TSource> ElementsAt<TSource>(
this IEnumerable<TSource> source, params int[] indices)
{
var indicesHashSet = new HashSet<int>(indices);
return source.Where((x, i) => indicesHashSet.Contains(i));
}
This implementation returns the elements in the order they are found in the source, and ignores duplicate indices.
Usage example:
viewModel.SLGDeptSalesList =
viewModel.SLGDeptSalesList.ElementsAt(1, 3, 4, 6).ToList();
Update: Here is an alternative implementation. This one returns the elements in the order of the indices, and in case of duplicate indices returns duplicate elements. In case of indices out of range no exception is thrown, but no elements are returned for those indices. So it is not guaranteed that the output sequence will have equal length with the requested indices.
public static IEnumerable<TSource> ElementsAt<TSource>(
this IEnumerable<TSource> source, params int[] indices)
{
return indices.Join(source.Select((Item, Index) => (Item, Index)),
i => i, p => p.Index, (_, p) => p.Item);
}
Update: And here is a scholastic variant of the previous implementation. This one guarantees that the requested indices and the output sequence will have equal length. An exception is thrown for indices outside the bounds of the source sequence.
public static IEnumerable<TSource> ElementsAt<TSource>(
this IEnumerable<TSource> source, params int[] indices)
{
var indicesHashSet = new HashSet<int>(indices);
var foundElements = source.Select((Item, Index) => (Item, Index))
.Where(p => indicesHashSet.Contains(p.Index))
.ToDictionary(p => p.Index, p => p.Item);
var notFoundIndices = indices.Where(i => !foundElements.ContainsKey(i)).ToList();
if (notFoundIndices.Count > 0)
throw new ArgumentOutOfRangeException(nameof(indices),
$"Indices [{String.Join(", ", notFoundIndices)}] " +
$"are outside the bounds of the {nameof(source)} sequence.");
return indices.Select(i => foundElements[i]);
}
Upvotes: 1
Reputation: 39277
You could use Contains
on an array:
var indices = new [] {1, 3, 4, 6};
viewModel.SLGDeptSalesList = viewModel.SLGDeptSalesList.Where(
(value, index) => indices.Contains(index)).ToList();
Using Contains
on an array is a common technique when you have queries you want to execute on SQL (not in your case, but in general), e.g. https://blogs.msdn.microsoft.com/alexj/2009/03/25/tip-8-how-to-write-where-in-style-queries-using-linq-to-entities/
Upvotes: 3
Reputation: 602
If your indexes are hardcoded in a list then you can just use the .Exists() within your Where. but if they are just
var indices = new List<int>(){1,3,4,6};
viewModel.SLGDeptSalesList = viewModel.SLGDeptSalesList.Where((value, index) => indices.Exists(index)).ToList();
Upvotes: 1
Reputation: 273065
You can do a Select
on your hardcoded list of indices, to transform them into corresponding elements in the viewModel.SLGDeptSalesList
list:
viewModel.SLGDeptSalesList = new List<int> { 1, 3, 4, 6 }.Select(x => viewModel.SLGDeptSalesList[x]).ToList();
Upvotes: 2