Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# GroupBy - Creating multiple grouping levels

Tags:

c#

linq

group-by

Given the following class:

public class Transaction
{
    public string Category { get; set; }
    public string Form { get; set; }
}

How do I get a grouping of transactions that are grouped by both the Category and the Form?

Basically I want it to output like so:

Category 1
    Form 1
        Transaction1
        Transaction2
        Transaction3
        ...
    Form 2
        Transaction1
        Transaction2
        Transaction3
        ...
Category 2
    Form 1
        Transaction1
        Transaction2
        Transaction3
        ...
    Form 2
        Transaction1
        Transaction2
        Transaction3
        ...
like image 607
Sam Avatar asked Sep 27 '13 03:09

Sam


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.

What is C full form?

History: The name C is derived from an earlier programming language called BCPL (Basic Combined Programming Language). BCPL had another language based on it called B: the first letter in BCPL.


3 Answers

Basically, the first group should contain every piece of information that you will need to group by later, and then on repeat groupings, remove one piece of information that is relevant for that grouping level. Do this as many times as you need.

ValueTuple can help a lot, since it lets you have a composite key that can be passed to another type. Otherwise with anonymous types, you'll need to rely on type inference to pass the groups to something else. One issue with ValueTuple though is that you can't have a 1-Tuple for some reason, so in that case you need to group by the single property and not use a tuple.

If you already have a hierarchical relationship in your data structure, then grouping by a Tuple might be unnecessary.

var groups =
   transactions
   .GroupBy(tran => (
      Category: tran.Category,
      Form: tran.Form
   )).GroupBy(group => group.Key.Form)
   .ToList();

The type gets complicated very fast, so use type inference and refactoring tools to avoid having to figure out the specific type, when possible. For example, just the above results in the type:

List<IGrouping<string, IGrouping<(string Category, string Form), Transaction>>>
like image 54
Dave Cousineau Avatar answered Oct 17 '22 07:10

Dave Cousineau


I ended up with the following, because the grouping need to be complete before the iteration over the collection.

Seed Some Transactions

var cats = new[] { "Category 1", "Category 2", "Category 3" };
var frms = new[] { "Form 1", "Form 2", "Form 3" };
var transactions = new List<Transaction>();

for (var i = 0; i <= 150; i++)
{
    transactions.Add(new Transaction
    {
        Category = i % 2 == 0 ? cats[0] : i % 3 == 0 ? cats[1] : cats[2],
        Form = i % 5 == 0 ? frms[0] : i % 7 == 0 ? frms[1] : frms[2]
    });
}

The Grouping

var groupedTransactions = transactions.GroupBy(x => x.Category)
    .Select(x => new
    {
        Category = x.Key,
        Forms = x.ToList()
            .GroupBy(y => y.Form)
    });

Write it to the Console

foreach (var group in groupedTransactions.OrderBy(x => x.Category))
{
    Console.WriteLine(group.Category);
    foreach (var form in group.Forms.OrderBy(x => x.Key))
    {
        Console.WriteLine("\t" + form.Key);
        foreach (var transaction in form)
        {
            Console.WriteLine("\t\t" + transaction.Id);
        }
    }
}
like image 41
Sam Avatar answered Oct 17 '22 05:10

Sam


Here is an example using nested foreach loops, I'm not sure how you would do this in a single string of linq statements, maybe with lots of selectmanys?

var transactions = new[]{
    new{Category = "1", Form = "1", Title = "Trans1" },
    new{Category = "1", Form = "1", Title = "Trans2" },
    new{Category = "1", Form = "1", Title = "Trans3" },
    new{Category = "1", Form = "2", Title = "Trans1" },
    new{Category = "1", Form = "2", Title = "Trans2" },
    new{Category = "1", Form = "2", Title = "Trans3" },
    new{Category = "2", Form = "1", Title = "Trans1" },
    new{Category = "2", Form = "1", Title = "Trans2" },
    new{Category = "2", Form = "1", Title = "Trans3" },
    new{Category = "1", Form = "3", Title = "Trans1" },
    new{Category = "1", Form = "3", Title = "Trans2" },
    new{Category = "1", Form = "3", Title = "Trans3" },
};

foreach(var byCategory in transactions.GroupBy(x => x.Category))
{
    Console.WriteLine(byCategory.Key);
    foreach(var byForm in byCategory.GroupBy(x => x.Form))
    {
        Console.WriteLine("\t" + byForm.Key);
        foreach(var trans in byForm)
        {
            Console.WriteLine("\t\t" + trans.Title);
        }
    }
}

Just because I was curious what it would look like I came up with the following, YOU SHOULD NOT USE THIS IN PRODUCTION CODE as it is ridiculous (if you do have a data structure like this it should be broken up into something like Dictionary<CategoryName, FormGroup> or something with meaningful types)

Dictionary<string, Dictionary<string, List<string>>> tooManyDictionaries = transactions
        .GroupBy(x => x.Category)
        .ToDictionary(
            catGroup => catGroup.Key,
            catGroup => catGroup
                .GroupBy(x => x.Form)
                .ToDictionary(
                    formGroup => formGroup.Key,
                    formGroup => formGroup.Select(x => x.Title).ToList()));
like image 32
BrandonAGr Avatar answered Oct 17 '22 06:10

BrandonAGr