Search code examples
c#listgrouping

Group List<Object> with keys and distinct values count


I'm trying to come up with a solution to group a given list of keys and values.

I need to group it like this: Key > Distinct Value > Count.

E.g.

XFN99 2 3 <= [CODE] [DISTINCT_VALUE] [OCCURANCE IN LIST]

I have a method that returns List<ObjectPart>. This object has a public string code and public int value.

I need to get count per distinct value in a key.

Example Input:

XFN999 2
XFN999 2
XFN999 2
XFN999 4
XFN999 8
XFN999 8
XFN999 8
BPN655 1
BPN675 2
BPN655 1

Desired output:

XFN999 2 => 3x
XFN999 4 => 1x
XFN999 8 => 3x
BPN655 1 => 2x
BPN675 2 => 1x

I tried to use LINQ my way to success but failed, as it sums values per key.

var distValsPerKey = S.getAllDiffs()
                      .Result
                      .GroupBy(x => x.code)
                      .Select(x => new { C_ = x.Key, V_ = x.Distinct().Count() });

Output with my solution:

BPN373 => 30
BPN374 => 35
BPN377 => 47
BPN378 => 43
BPN387 => 67
BPN388 => 49
BPN653 => 10
BPN654 => 15
BPN699 => 40
BPN700 => 45
BPN711 => 68
BPN723 => 13
BPN724 => 11
BPN853 => 5
BPN854 => 6
BPN877 => 99
BPN878 => 94
BPN505 => 92
BPN507 => 570
BPN508 => 617

My solution basically sums values for key, but I need to sum/count distinct values per key.

Example below:

Getting input of List<ObjectPart>:

public async Task<List<ObjectPart>> getAllDiffs()
{
    List<ObjectPart> TEMP = new List<ObjectPart>();

    await Task.Run(() =>
            {
                using (IngresConnection CONN = new IngresConnection())
                {
                    string QUERY = SELECT_REDACTED;

                    try
                    {
                        using (IngresCommand CMD = new IngresCommand())
                        {

                            CONN.ConnectionString = "host=; userID=; pwd=; database=;";
                            CMD.Connection = CONN;
                            CMD.CommandText = QUERY;
                            CONN.Open();

                            using (IngresDataReader READ = CMD.ExecuteReader())
                            {
                                while (READ.Read())
                                {
                                    ObjectPart OP = new ObjectPart();

                                    OP.wenr = READ.GetValue(0).ToString().Trim();
                                    OP.difference = Convert.ToInt32(READ.GetInt32(1));

                                    TEMP.Add(OP);
                                }
                                READ.Close();
                                CONN.Close();
                            }
                        }
                    }
                    catch (Exception E)
                    {
                        CONN.Close();
                        CONN.Dispose();
                    }
                }
            });

    List<ObjectPart> OPS_SORTED = TEMP.OrderBy(m => m.code).ToList();

    return OPS_SORTED;
}

LINQ the input to get desired output:

 var distValsPerKey = S.getAllDiffs()
                       .Result
                       .GroupBy(x => x.code)
                       .Select(x => new { C_ = x.Key, V_ = x.Distinct().Count() });
        
foreach(var OP in distValsPerKey)
{
    Console.WriteLine($"{OP.C_} = > {OP.V_}");
}

Solution

  • You want to group by code AND value, right? So do that in your GroupBy:

    var distValsPerKey = S.getAllDiffs().Result
        .GroupBy(x => new { x.code, x.value })
        .Select(x => new { C_ = x.Key, V_ = x.Distinct().Count() });
    

    The rest of your code should work as it is, although if you output the Key of the GroupBy object it won't have quite the format you want. I get results as below:

    { code = XFN999, value = 2 } => 3
    { code = XFN999, value = 4 } => 1
    { code = XFN999, value = 8 } => 3
    { code = BPN655, value = 1 } => 2
    { code = BPN675, value = 2 } => 1
    

    If that isn't what you want and you want C_ to be the text string in your example output then change the Select:

    var distValsPerKey = S.getAllDiffs().Result
        .GroupBy(x => new { x.code, x.value })
        .Select(x => new { C_ = $"{x.Key.code} {x.Key.value}", 
                           V_ = x.Distinct().Count() });
    

    This gives output:

    XFN999 2 => 3
    XFN999 4 => 1
    XFN999 8 => 3
    BPN655 1 => 2
    BPN675 2 => 1