2

I'd like to construct a LINQ GroupBy statement according to the following decimal categories: 0-50, 50-100, 100-250, 250-above. I found Group by variable integer range using Linq which discusses how to use a variable range, but that query has a finite upper bound. My query needs to be able to handle everything over 250 as one group. I tried using decimal.maxValue as my upper bound, but the query couldn't handle it, I suppose because the value is larger than what NHibernate can handle. Ideally, I'd like to do this without specifying a max value so the query is independent of the database.

Edit: I'm pretty sure I can do this by using a array of floor values and following the pattern in the link. But I'm curious if there is a kind of catch-all group-by construct.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Vish
  • 453
  • 6
  • 18

1 Answers1

2

Edit:

You changed your OP to state that you could use a floor function, but you wanted to find out about a default grouping.

Mathematically a floor function is equivalent. In the case of ceiling, the lower bound for the data they used is presumably 0. In the case of floor, the logical upper bound is positive infinity (effectively it ends up being the highest value the DB supports, since integers don't support the concept of infinity). It gets you where you want to go.

If you want something that might be more applicable to other situations, you could try something like this:

items.GroupBy(item =>
    (
        floors.FirstOrDefault(floor => floor <= item)
            ?? "Default"
        )
        .ToString()
    );

It probably wouldn't work in Linq to NHibernate, as I don't think this would map well to SQL. Instead you could import the whole set into memory first (.ToList()), and then add your grouping as a Linq to Objects query.

It doesn't make a lot of sense to use it in this situation, but it might in the case of non-number-line groupings:

var groups = new HashSet<string>
{
    "Orange",
    "Green",
    "Mauve",
};

items.GroupBy(item =>
    groups.Contains(item.Color)
        ? item.Color
        : "Default"
    );

Before Edit:

You could simply reverse the logic and you'll automatically include everything below a certain value.

var floors = new[] { 250, 100, 50, 0 };
var groupings = items.GroupBy(item => floors.First(floor => floor <= item));

How it works:

  • Take an item 270.
    The first item in the list would be the first bucket it falls under. This is because 250 <= 270.

  • Take an item 99.
    The third item in the list would be the first bucket it falls under. 250 is not less than 99. 100 is not less than 99. But 50 is less than 99.

  • An item 50 would fall into the third bucket.
    It is less than 250 and 100, but equal to 50.

Doesn't quite match the description in your question:

Your group description is a bit broken. You'd have to bucketize them separately for this algorithm to work. There would be a 0-50 bucket, 51-100 bucket, etc. Or a 0-49 bucket, a 50-99 bucket, etc.

A 0-50 bucket, and a 50-100 bucket couldn't exist together.

Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
  • Yeah. I edited my question to reflect that I've thought that out. So I guess there is no "default" group-by command? – Vish Nov 03 '11 at 18:24
  • I understand the mathematical equivalence. It was more a question of LINQ semantics i.e can I accomplish my task this way, not how do I accomplish my task. But you answered, so I've marked yours as accepted. Thanks. edit: I actually had to pull my whole object set first for other reasons. But I actually think that's less clear than an implicit upper bound! – Vish Nov 03 '11 at 18:34
  • @Vish: Edited my question to reflect your needs. Let me know if I need to clear anything up. – Merlyn Morgan-Graham Nov 03 '11 at 18:41
  • Nope, that was perfect. Thank you for being so accommodating. – Vish Nov 07 '11 at 18:51