Maruf
Maruf

Reputation: 456

Group list by multiple conditions, LINQ

I have Book class:

public class Book
{
    public int Id {get; set;}
    public string Name {get; set;}
    public deciaml Price {get; set;}
}

And List of books:

List<Book> books = new List<Book>
    {
        new Book
        {
            Id = 1,
            Name = "AA",
            Price = 19.0000M
        },
        new Book
        {
            Id = 2,
            Name= "BB",
            Price = 18.0000M
        },
        new Book
        {
            Id = 3,
            Name = "CC",
            Price = 30.0000M
        },
        new Book
        {
            Id = 4,
            Name = "DD",
            Price = 9.0000M,
        },
    };

Now I want to group list by price rate, like cheap, medium, expensive:

for example, cheap = 10, medium = 20, expensive = 30 grouped list would be like:

List<(decimal category, List<Book> books)

    {
       category: 10
       books: List<Book>() {Book {4, "DD", 9.0000M}},
       category: 20
       books: List<Book>() {Book {1, "AA", 19.0000M}, Book {2, "BB", 18.0000M}},
       category: 30
       books: List<Book>() {Book {3, "CC", 30.0000M}},
    } 

UPDATE

books.GroupBy(book => book.Price).Select(group => (group.Key, group.ToList()));

I can get grouped list of books but, can not group by multiple conditions.

UPDATE

Thank you all for quick answers, seems DotNet Developers answer shorter and sweet.

books.GroupBy(book => book.Price <= cheap ? cheap :
                    (book.Price > cheap && book.Price <= middle ? middle : expensive))
         .Select(group => (group.Key, group.ToList()))

Upvotes: 0

Views: 686

Answers (4)

Astrid E.
Astrid E.

Reputation: 2872

If you are not necessarily bound by specific category values, but rather want to group your books by price rates that are divisible by 10 (i.e. 10, 20, 30, 40, and so on), you could achieve this by rounding the Price up to the nearest 10 and use that as your group-by-value.

Here is an example, using Math.Ceiling() to do the rounding:

Math.Ceiling(value / 10) * 10 // Round 'value' upwards to nearest 10

Applied as the group-by-condition:

List<(decimal Category, List<Book> Books)> groupedBooks = books
    .GroupBy(book => Math.Ceiling(book.Price / 10) * 10,
        (category, booksInCategory) => ( 
            Category: category, 
            Books: booksInCategory.ToList() ))
    .OrderBy(gr => gr.Category)
    .ToList();

Using your provided example list for books, the resulting groupedBooks contains:

Category: 10
Book: { Id: 4 Name: DD Price: 9.0000 }

Category: 20
Book: { Id: 1 Name: AA Price: 19.0000 }
Book: { Id: 2 Name: BB Price: 18.0000 }

Category: 30
Book: { Id: 3 Name: CC Price: 30.0000 }

Example fiddle here.

Upvotes: 0

DotNet Developer
DotNet Developer

Reputation: 3018

With ternary conditional operator

books.GroupBy(b => b.Price < 10 ? 10 : (b.Price < 20 ? 20 : 30));

Upvotes: 2

LongChalk_Rotem_Meron
LongChalk_Rotem_Meron

Reputation: 803

First - add category to the list

var newBookList = BookList.Select(x=> new { Book = x, 
                                     Category = x.Price <= 10 ? 10 : 
                                                x.Price <= 20 ? 20 : 
                                                30 } ).ToList();

then Group By this Category :

var GroupedBooks = newBookList.GroupBy(x=>x.Category);
foreach(var category in GroupedBooks.Keys) {
         Console.WriteLine("category : {0}", category);
         foreach(var b in GroupedBooks[Keys].Select(x=>x.Book))
          Console.Write("Book {0}, \"{1}\", {2}",b.Id, b.Name, b.Price);
}

something like that?

Upvotes: 0

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131334

The GroupBy operator expects a function or expression that returns the group key. You can use this to return the group "name" based on the price :

var bins=books.GroupBy(b=> b.Price switch {  
                          <=10          =>"cheap", 
                          >10 and <= 20 => " medium", 
                          _             => "expensive"})
              .ToDictionary(g=>g.Key,g=>g);

The result is a dictionary with each category and its contents :

Console.WriteLine(JsonSerializer.Serialize(bins));
-------
{
    " medium":[{"Id":1,"Name":"AA","Price":19.0000},{"Id":2,"Name":"BB","Price":18.0000}],
    "expensive":[{"Id":3,"Name":"CC","Price":30.0000}],
    "cheap":[{"Id":4,"Name":"DD","Price":9.0000}]
}

I used pattern matching in GroupBy to fit all conditions in a single lambda. This could be a separate function though :

string BookToBins(Book b)
{

    if (b.Price <= 10) return "cheap";
    if (b.Price <= 20) return "medium";
    return "expensive";
}

...
var bins=books.GroupBy(BookToBins)
              .ToDictionary(g=>g.Key,g=>g);

Upvotes: 1

Related Questions