Search code examples
c#linqcountlinq-group

LINQ group by sequence and count with sorting


I am searching a best performance method to group and count sequences with sorting using LINQ. I will be processing files even bigger than 500 MBs so performance is most important key in that task.

List<int[]> num2 = new List<int[]>();
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 35, 33 });
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 3967, 11 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 200, 2 });

The result have to be like this:

[35,   44]  => 2
[200,  22] => 2
[35,   33] => 1
[35,   44] => 1
[3967, 11] => 1
[200,  2 ] => 1

I have done something like this:

        Dictionary<int[], int> result2 = (from i in num2
                                       group i by i into g
                                       orderby g.Count() descending
                                       select new { Key = g.Key, Freq = g.Count() })
                          .ToDictionary(x => x.Key, x => x.Freq);

        SetRichTextBox("\n\n Second grouping\n");

        foreach (var i in result2)
        {
            SetRichTextBox("\nKey: ");
            foreach (var r in i.Key)
            {
                SetRichTextBox(r.ToString() + "  ");
            }

            SetRichTextBox("\n  Value: " + i.Value.ToString());

        }

But it is not working properly. Any help?


Solution

  • For arrays of length 2, this will work.

    num2.GroupBy(a => a[0])
        .Select(g => new { A0 = g.Key, A1 = g.GroupBy(a => a[1]) })
        .SelectMany(a => a.A1.Select(a1 => new { Pair = new int[] { a.A0, a1.Key }, Count = a1.Count() }));
    

    I think that should give you optimal performance; you could also try an .AsParallel() clause after your first Select statement.

    This strategy (grouping successively by the n-th element of the arrays) generalises to arrays of arbitrary length:

    var dim = 2;
    
    var tuples = num2.GroupBy(a => a[0])
        .Select(g => new Tuple<int[], List<int[]>>(new [] { g.Count(), g.Key }, g.Select(a => a.Skip(1).ToArray()).ToList()));
    
    for (int n = 1; n < dim; n++)
    {
        tuples = tuples.SelectMany(t => t.Item2.GroupBy(list => list[0])
            .Select(g => new Tuple<int[], List<int[]>>(new[] { g.Count() }.Concat(t.Item1.Skip(1)).Concat(new [] { g.Key }).ToArray(), g.Select(a => a.Skip(1).ToArray()).ToList())));
    }
    
    var output = tuples.Select(t => new { Arr = string.Join(",", t.Item1.Skip(1)), Count = t.Item1[0] })
        .OrderByDescending(o => o.Count)
        .ToList();
    

    which generates an output of

    Arr = "35, 44", Count = 2
    Arr = "200, 22", Count = 2
    Arr = "35, 33", Count = 1
    Arr = "200, 2", Count = 1
    Arr = "3967, 11", Count = 1
    

    in your example. I'll let you test it for higher dimensions. :)

    You should be able to parallelise these queries without too much difficulties, as the successive groupings are independent.