0

How can you determine the current items position whilst looping through the collection?

I'm working through decision data, grouped by each client, but I have some business logic which depends on the "position" in the set, i.e. 1st, 2nd, 3rd, etc. in conjunction with other properties of the record, e.g. if it's the 3rd decision about a client and their rating in the instance is A then ...

var multiples = from d in context.Decision_Data
                group d by d.Client_No
                    into c
                    where c.Count() > 1
                    select c;

foreach (var grouping in multiples)
{
    foreach (var item in grouping)
    {
        // business logic here for processing each decision for a Client_No
        // BUT depends on item position ... 1st, 2nd, etc.
    }

UPDATE: I appreciate I could put a counter in and manually increment, but it feels wrong and I'd of thought there was something in .NET to handle this ??

SteveC
  • 13,636
  • 21
  • 86
  • 155
  • Why not just use a `for`-loop instead of `foreach`? – sloth Jul 25 '12 at 09:39
  • possible duplicate of [How do you get the index of the current iteration of a foreach loop?](http://stackoverflow.com/questions/43021/how-do-you-get-the-index-of-the-current-iteration-of-a-foreach-loop) – sloth Jul 25 '12 at 09:39
  • @BigYellowCactus A `for` loop isn't possible on `IEnumerable` due to the lack of an indexer. – Adam Houldsworth Jul 25 '12 at 09:46
  • @AdamHouldsworth I think it would not be too hard to call `ToList()` on the `IEnumerable` :-) – sloth Jul 25 '12 at 09:52

4 Answers4

4

Something like this:

foreach (var grouping in multiples)
{
    foreach (var x in grouping.Select(index,item) => new {index, item})
    {
        // x.index is the position of the item in this group
        // x.item is the item itself
    }
}

Side note: you can make the implementation of your LINQ query a bit more efficient. Count() > 1 will completely enumerate each group fully, which you are likely to do in the foreach anyway. Instead you can use Skip(1).Any(), which will stop iterating the group as soon as it finds two items. Obviously this will only make a real difference for (very) large input lists.

var multiples = from d in context.Decision_Data
                group d by d.Client_No
                    into c
                    where c.Skip(1).Any() 
                    select c;
SteveC
  • 13,636
  • 21
  • 86
  • 155
jeroenh
  • 24,301
  • 9
  • 67
  • 99
3

There isn't anything offered by the standard foreach. Simply maintain an external count.

There is an overload on the Enumerable.Select extension method that provides the index of the current item:

http://msdn.microsoft.com/en-us/library/bb534869

But without knowing what your code is trying to do in the foreach I cannot really offer an example of using it. In theory you could project an anonymous type that has the index stored and use that later on with the foreach. It appears that jeroenh's answer went down this route.

Community
  • 1
  • 1
Adam Houldsworth
  • 60,104
  • 9
  • 137
  • 177
0

As Adam stated, you could either go with Adams solution, or do a ToList() on the query to be able to do

multiples.IndexOf(grouping)
DerApe
  • 2,881
  • 2
  • 31
  • 48
0

I fail to see you you can have any certainty about your decisions order. I'm guessing your data come from a long-term data source (e.g. data base or such) and that you doesn't have any control on the order in which the decision are fetched from the data source, especially after applying a "group by".

I would add an "order" field (or column) to the Decision entity to track the order in which the decision were made that would be set while adding the Decision to the data source. That way, you could directly use this field in your business logic.

There must be many ways to achieve the tracking of decision order but without, you can't even be sure in what order they have been made.

SteveC
  • 13,636
  • 21
  • 86
  • 155
Paciv
  • 1,428
  • 9
  • 15