2

I need to group data using a custom condition, which returns true or false. The complexity is, I need to group only the items which return true and do not group the others.

If I use:

var result = data.GroupBy(d => new { Condition = d.SomeValue > 1 });

all the items get grouped by true/false.

Now I have this statement that works:

var result = data.GroupBy(d => new { Condition = d.SomeValue > 1 ? "true" : FunctionToGetUniqueString() });

UPDATE: Note, the other items (which are false) must also be in the grouping, but go separately (one per group).

But I think it is a bit dirty solution, so I though that maybe there is something more elegant?

net_prog
  • 9,921
  • 16
  • 55
  • 70

2 Answers2

3

I think your solution works; I would just be sure to add detailed comments to explain what the expected result is.

If you want to use a "built-in" string generator rather than creating your own you could use Guid.NewGuid():

var result = data.GroupBy(d => new { Condition = d.SomeValue > 1 
                                     ? "true"
                                     : Guid.NewGuid().ToString() })

Also you don't need an anonymous type unless you want to use the Condition value later:

var result = data.GroupBy(d => d.SomeValue > 1 
                               ? Guid.Empty 
                               : Guid.NewGuid() )
D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • I think `Guid.NewGuid()` is a good idea, but then I'd suggest to use `Guid.Empty` instead of `"_true"` as key for the items that don't match the condition – Stephan Bauer Aug 22 '13 at 12:42
  • Is it guaranteed that `Guid.NewGuid()` will always return a unique string? – net_prog Aug 22 '13 at 12:42
  • `Guid.NewGuid()` will return a `Guid`, not a `string`. And yes, it should always return a unique value – Stephan Bauer Aug 22 '13 at 12:44
  • @net_prog GUID stands for Globally Unique IDentifier, so yes it is virtually[*](http://stackoverflow.com/questions/39771/is-a-guid-unique-100-of-the-time) guaranteed to be unique – D Stanley Aug 22 '13 at 12:47
0

The easiest way would be something like this:

var result = data
    .GroupBy(d => d.SomeValue > 1 ? 0 : d.ID);

Assuming ID is an int property that is unique among the items in your collection. Of course can adapt this technique pretty easily to work with if you've got a unique string property, etc. If you have no unique property, you can always do something like this:

var result = data
    .Select((d, i) => new { d, i })
    .GroupBy(x => x.d.SomeValue > 1 ? -1 : x.i, x => x.d);

This will work in Linq-to-Objects, but I'm not sure about other Linq providers (e.g. Linq-to-SQL, Linq-to-Entities). For those cases, D Stanley's answer might be the only way to go, but for simple Linq queries, this would be more efficient than generating a random Guid for every non-grouped item.

Of course, if this seems inelegant, you could always wrap it in an extension method. Note that I return an IEnumerable<IEnumerable<T>> rather than IEnumerable<IGrouping<TKey, T>>, since the key really is not relevant here anyway:

public static IEnumerable<IEnumerable<T>> GroupWhere<T>(
    this IEnumerable<T> set,
    Func<T, bool> grouping)
{
    return set
        .Select((d, i) => new { d, i })
        .GroupBy(x => grouping(x.d) ? -1 : x.i, x => x.d);
}

Or this, which, which is has slightly different semantics (grouped items always appear last) but should be bit more efficient:

public static IEnumerable<IEnumerable<T>> GroupWhere<T>(
    this IEnumerable<T> set,
    Func<T, bool> grouping)
{
    List<T> list = new List<T>();
    foreach(var x in set)
    {
        if (!grouping(x))
        {
            yield return new[] { x };
        }
        else 
        {
            list.Add(x);
        }
    }

    yield return list;
}

Either way you can use this just like this:

var result = data.GroupWhere(d => d.SomeValue > 1);
Community
  • 1
  • 1
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331