tags:

views:

144

answers:

2

Is it possible using Linq to create a group where items fall into more than one group?

Using the following trivial example:

    public class Data
    {
        public string ID;
        public int From;
        public int To;
    }

And this list:

    List<Data> data = new List<Data>() 
    {
        new Data() { ID = "A", From = 1, To = 3 }, // Call this A
        new Data() { ID = "B", From = 1, To = 2 }, // Call this B
        new Data() { ID = "C", From = 2, To = 3 }  // Call this C
    };

I'd like to group by each possible integer in the ranges From and To (though instead of finding the min + max I could supply the query with the range I want, for example 1 to 3), and in each group would be a reference to the Data instance where the grouping int fits in its range.

Hard to explain, easier to show each group and the instances I'd expect in each:

[Group 1] 1 - A, B
[Group 2] 2 - A, B, C
[Group 3] 3 - A, C

Is this possible? Or must groups be mutually exclusive?

Edit: Is a Join the way to do this?

int[] range = new int[] { 1, 2, 3 };

var query = from d in data
    from r in range
    where d.From <= r && d.To >= r
    group d by r into g
    select g;

Is this possible without forumlating the range first of all?

Edit #2: Using answer two and refactoring, you can do:

 var result = Enumerable.Range(1,3)
                        .Select(i => new
                                     {
                                       Key = i,
                                       Values = data.Where(d => i >= d.From &&
                                                           i <= d.To)
                                      });

This has the added benefit of also returning digits in the range without any Data instances that fit, which is what I need.

+2  A: 

Instead of a GroupBy, try a Join. I believe groupings are mutually exclusive. Join your desired "grouping" values to your list of data on From <= Value AND To >= Value

EDIT (In answer to your comment) You can get your range like this:

int min = data.Min(a => a.From);
int max = data.Max(a => a.To);
List<int> range = new List<int>();
for (int i = min; i <= max; i++)
    range.Add(i);
Eric Mickelsen
I think you could be right, I've edited my question to show something I tried which has the desired affect. Though would be nice if I didn't have to create a range first?
PirateKitten
+1  A: 

GroupBy groups the item such that no item is in more than one group.

This should do what you want:

var result = data.SelectMany(d => Enumerable.Range(d.From, d.To - d.From + 1))
                 .Distinct()
                 .Select(i => new
                              {
                                  Key = i,
                                  Values = data.Where(d => i >= d.From &&
                                                           i <= d.To)
                              });

I don't know if this is a big improvement over the solution you already have, though.

dtb
Cheers, that's quite useful actually, I've refactored it slightly and added it to my question. I was trying to get the situation where I'd also have numbers in the range be returned even if they didn't have any data entries, and this got me there.
PirateKitten