Love
Love

Reputation: 43

LINQ - How do I compare the first two items in a group then the next?

I have two collections, A and B. Everything that goes into collection A should sum to 0 (these have unique identifiers that push them into Collection A) while Collection B doesn't have to sum to 0. For example:

item1 = 200; 
item2 = -200; 
item3 = 200

These don't sum to 0 but have the same unique id's and are now in Collection A. I want to group them and compare the first two items and if they sum to 0, I move to the next items in the group and if those items don't sum to 0, I want to move them to collection B.

This is what I have now:

var grp= colA.AsEnumerable()
        .Where(a => a.Field<string>("unique_id") != null)
        .GroupBy(b=> b["unique_id"])
        .Where(c=> c.Count() > 1).ToList();
foreach(var d in grp)
{
  var sum = d.AsEnumerable().Sum(e => e.Field<decimal>("amount"));
}
if(sum != 0){//compare rows in group}

This successfully groups the items that don't equal 0 but I'm stuck at how to compare item1 and item2 then item3 so that item3 can be moved to collection B and Collection A will then be summed to 0.

Upvotes: 1

Views: 485

Answers (2)

Harald Coppoolse
Harald Coppoolse

Reputation: 30464

If you use Skip / Take to enumerate over your sequence, you'll start at the front of your sequence over and over again. This can be done smarter!

its a bit unclear what you want in the following sequence:

200, -200, 10, 11, -11, 12, -12, 13, -13

200 and -200 stay in collection A. But do you remove only 10, and let the pairs [11, -11], [12, -12], [13, -13] in A, or do your remove [10, 11], [-11, 12], [-12, 13], -13?

let's suppose you want the latter. You need to split your input sequence into pairs of two. For this, let's make a re-usable function.

Input: a sequence of items and a splitSize Output: a sequence of ICollections where every ICollection has splitsize (except the last)

So input: 0 1 2 3 4 5 6 7 8 9 and SplitSize 3 Output: [0 1 2] [3 4 5] [6 7 8] [9]

I create this as an extension method, so you can use it in a LINQ concatenation. See extension methods demystified

public static IEnumerable<IList<TSource>> Split<TSource>(this IEnumerable<TSource> source,
    int splitSize)
{
    // todo: check for non-null source and positive splitSize

    var enumerator = source.GetEnumerator();
    while (enumerator.MoveNext())
    {
       // still elements to process. Create and fill the next splitList
       List<TSource> splitList = new List<TSource>(splitSize);
       splitList.Add(enumerator.Current()
       while (splitList.Count < splitSize && enumerator.MoveNext())
       {
           // still elements to add:
           splitList.Add(enumerator.Current);
       }
       // if here: either no more elements, or splitList full.
       yield return splitList;
    }
}

With the help of this function we can split your input in pairs [0 1] [2 3] [4 5] and check whether they should be in the collection of zero-sum or non-zero-sum:

void CreateZeroSumCollections<TSource>(IEnumerable<TSource> source,
     Func<TSource, int> keySelector,
     out IList<TSource> zeroCollection,
     out IList<TSource> nonzeroCollection)
{
    var zeroCollection = new List<TSource>();
    var nonzeroCollection = new List<TSource>();

    var splitSource = source.Split(2);
    foreach (var splitElement in splitSource)
    {
        // the splitElement has a length of 2 or less
        // Check if the sum is zero
        if (splitElement.Count == 2 
            && keySelector(splitElement[0]) == -keySelector(splitElement[1])
        {   // 2 elements, and sum is zero
            zeroCollection.AddRange(splitElement);
        }
        else
        {   // either only 1 element or non zero sum
            nonzeroCollection.AddRange(splitElement);
        }
    }
}

For this your sequence has to be enumerated only once.

Upvotes: 0

Seva
Seva

Reputation: 1739

Would something like this help you?

grp.ForEach(g => 
{
    int stepNumber = 0;
    int step = 2;
    var target = g.Skip(stepNumber * step).Take(step);

    if (target.Sum(x => x.Field<decimal>("amount")) != 0)
    {
        foreach (var item in target.Select(x => x))
        {
            colA.Rows.Remove(item);
            colB.Rows.Add(item);
        }
    }

    stepNumber ++;
});

Upvotes: 1

Related Questions