JSkyS
JSkyS

Reputation: 443

I need to get multiple index's instead of one using linq

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

Answers (4)

Theodor Zoulias
Theodor Zoulias

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

Ian Mercer
Ian Mercer

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

Mexicoder
Mexicoder

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

Sweeper
Sweeper

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

Related Questions