Search code examples
c#.netlinq.net-4.6.1

Group and separate list


I have below entity structure

public class Item
{
    public EnumType Type { get; set; }
    public int Price { get; set; }

}

public enum EnumType
{
    A =1,
    B=2,
    C =3
}

I have a list of items as follow

   var items = new List<Item>
        {                 
            new Item{ Price=5, Type= EnumType.B},
            new Item{ Price=5, Type= EnumType.B},
            new Item{ Price=5, Type= EnumType.B},
            new Item{ Price=10, Type= EnumType.B},
            new Item{ Price=10, Type= EnumType.B},
            new Item{ Price=10, Type= EnumType.B},
            new Item{ Price=15, Type= EnumType.C},
            new Item{ Price=15, Type= EnumType.C},
            new Item{ Price=15, Type= EnumType.C},
            new Item{ Price=15, Type= EnumType.C},
            new Item{ Price=15, Type= EnumType.C}
        };

If the price and type are same, based on type it need to exclude every nth item from the list and then calculate the sum. i.e type B = 3, Type C = 4

Which means in above sample data, since there are 3 items each in type B once it group by price and type it need to exclude every 3rd item when calculate sum. So sum for type B will be 5+5+10+10 and sum for type C will be 15+15+15+15

I tried using modular but seems its not the correct direction

I have tried this so far

  static int GetFactorByType(EnumType t)
    {
        switch(t)
        {
            case EnumType.A:
                return 2;
            case EnumType.B:
                return 3;
            case EnumType.C:
                return 4;
            default:
                return 2;
        }
    }

  var grp = items.GroupBy(g => new { g.Type, g.Price }).Select(s => new
        {
           type= s.Key.Type,
           price = s.Key.Price,
           count = s.Count()
        }).Where(d => d.count % GetFactorByType(d.type) == 0).ToList();

Solution

  • Here's one solve:

            //track the type:nth element discard
            var dict = new Dictionary<EnumType, int?>();
            dict[EnumType.B] = 3;
            dict[EnumType.C] = 4;
    
            //groupby turns our list of items into two collections, depending on whether their type is b or c
            var x = items.GroupBy(g => new { g.Type })
              .Select(g => new   //now project a new collection 
                {
                    g.Key.Type,  //that has the type 
                    SumPriceWithoutNthElement = //and a sum
                        //the sum is calculated by reducing the list based on index position: in where(v,i), the i is the index of the item. 
                        //We drop every Nth one, N being determined by a dictioary lookup or 2 if the lookup is null
                        //we only want list items where (index%N != N-1) is true
                        g.Where((v, i) => (i % (dict[g.Key.Type]??2)) != ((dict[g.Key.Type] ?? 2) - 1))
                        .Sum(r => r.Price) //sum the price for the remaining
                }
            ).ToList(); //tolist may not be necessary, i just wanted to look at it
    

    It seemed to me like your question words and your example are not aligned. You said (and did in code):

    If the price and type are same, based on type it need to exclude every nth item from the list and then calculate the sum. i.e type B = 3, Type C = 4

    Which to me means you should group by Type and Price, so B/5 is one list, and B/10 is another list. But you then said:

    Which means in above sample data, since there are 3 items each in type B once it group by price and type it need to exclude every 3rd item when calculate sum. So sum for type B will be 5+5+10+10

    I couldn't quite understand this. To me there are 3 items in B/5, so B/5 should be a sum of 10 (B/5 + B/5 + excluded). There are 3 items in B/10, again, should be (B/10 + B/10 + excluded) for a total of 20.

    The code above does not group by price. It outputs a collection of 2 items, Type=B,SumWithout=30 and Type=C,SumWithout=60. This one groups by price too, it outputs a 3 item collection:

            var x = items.GroupBy(g => new { g.Type, g.Price })
              .Select(g => new    
                {
                    g.Key.Type,  
                    g.Key.Price,
                    SumPriceWithoutNthElement = 
                        g.Where((v, i) => (i % (dict[g.Key.Type]??2)) != ((dict[g.Key.Type] ?? 2) - 1))
                        .Sum(r => r.Price)                 }
            ).ToList(); 
    

    The items are Type=B,Price=5,SumWithout=10 and Type=B,Price=10,SumWithout=20 and Type=C,Price=15,SumWithout=60

    Maybe you mean group by type&price, remove every 3rd item (from b, 4th item from c etc), then group again by type only and then sum

    This means if your type B prices were

    1,1,1,1,2,2,2,2,2
        ^       ^
    

    we would remove one 1 and one 2 (the Ines with arrows under them), then sum for a total of 9. This is different to removing every 3rd for all type b:

    1,1,1,1,2,2,2,2,2
        ^     ^     ^
    

    ?

    In which case, maybe group by Type/sum again the SumWithout output from my second example


    I did consider that there might be a more efficient ways to do this without LINQ.. and it would nearly certainly be easier to understand the code if if were non LINQ - LINQ isn't necessarily a wonderful magic bullet that can kill all ptoblems, and even though it might look like a hammer with which every problem can be beaten, sometimes it's good to avoid

    Depending on how you intended the problem to be solved (is price part of the group key or not) building a dictionary and accumulating 0 instead of th price every Nth element might be one way.. The other way, if price is to be part of the key, could be to sum all the prices and then subtract (count/N)*price from the total price