jAntoni
jAntoni

Reputation: 621

C# Linq to Group to a list if a Condition meets else do not group

I have a list
My Requirement

I need a LINQ lambda query to Group to a list if a Condition meets else do not group.

i.e On a condition I want it to be grouped
else it should not be grouped

I have searched net - I get details on grouping on condition and I couldn't get and understand on how the remaining item should be included without grouping.

Some info found on net was for grouping Conditionally - but with that those items not meeting conditions do not include in the resultant list.

For Example

List = [{1,"a"},{40,""),{9,"a"},{52,"b"),{2,"b"},{99,""),{88,"b"}]

The expected resultant list is to be grouped by a,b but "" should not be grouped

ResultantList = Group[0] ==> [{1,"a"}
                             {9,"a"}],
                 Group[1] ==>[ {52,"b"),
                               {2,"b"},
                               {88,"b"}] ,
                 // all other items which is "" should be included without groups
                Group[3] [ {40,""}]  
                 Group[4][ {99,""} ] 

What I have tried

var resultantList =  sigList
                    .GroupBy(s => s.SignalGroup)
                    .Select(grp => grp.ToList())
                     //.Where(g => !g.Any(grp => grp.SignalGroup == ""))
                     .ToList();

With the above as expected

  1. Uncommenting Where clause groups only a and b==> All those items with empty value ( "" ) does not get included

  2. Commenting Where clause groups a,b and "" string to make a list with 3 groups(a,b and "").

Upvotes: 2

Views: 16251

Answers (3)

Slava Utesinov
Slava Utesinov

Reputation: 13488

You can solve your problem this way, without redundant Id or Guid.NewGuid()at all:

public class Test
{
    public string property { get; set; }
    public string name { get; set; }
}

public static void Main()
{
    var items = new List<Test>()
    {
        new Test{ property = "1", name = "1" },
        new Test{ property = "1", name = "2" },
        new Test{ property = "2", name = "3" },
        new Test{ property = "2", name = "4" },
        new Test{ property = "", name = "5" },
        new Test{ property = "", name = "6" }
    };

    var result = items
            .GroupBy(x => x.property == "")
            .SelectMany(x =>
                !x.Key ?
                    x.GroupBy(y => y.property).Select(y => y.ToList()).ToList() 
                    :
                    x.Select(y => new List<Test> { y }).ToList()
                ).ToList();

    foreach (var res in result)
    {
        Console.WriteLine("Group " + res.First().property + ":");
        foreach (var item in res)
            Console.WriteLine(item.name);
    }
}

Upvotes: 0

Harald Coppoolse
Harald Coppoolse

Reputation: 30454

In your example you specify that the items that you don't want to group have an empty string value.

A solution where you can specify which values should not be grouped:

static class EnumerableExtensions
{
    public static IEnumerable<IGrouping<TKey, TSource>> SpecialGroupBy(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector,
        TKey dontGroupKeyValue)
    {
        // Group all items according to the keySelector,
        // return all groups, except the group with the key dontGroupKeyValue

        var groups = source.GroupBy(keySelector);
        foreach (IGrouping<TKey, TSource> group in groups)
        {
            if (group.Key != dontGroupKeyValue)
            {  // return this as a group:
               return group;
            }
            else
            {   // return every element of the group as a separate IGrouping
                foreach (TSource element in group)
                {
                   return Grouping.Create(dontGroupKeySelector, element)'
                }
             }
         }
    }
}

For this I use an adaptation of a helper class I found here in StackOverFlow. This implementation hides the actual implementation of the IGrouping. It only exposes the IGrouping required properties and functions to create it. If you do need to Create IGrouping regularly consider putting this in some base library.

public class Grouping<TKey, TElement> : IEnumerable<TElement>, IGrouping<TKey, TElement>
{
    public static IGrouping<TKey, TElement> Create(TKey key, TElement element)
    {
        return new Grouping<TKey, TElement>()
        {
            Key = key,
            Elements = new TElement[] {element},
        };
    }
    public static IGrouping<TKey, TElement> Create(TKey key,
        IEnumerable<TElement> elements)
    {
        return new Grouping<TKey, TElement>()
        {
            Key = key,
            elements = elements,
        };
    }

    private Grouping() { }

    public TKey Key { get; private set; }
    private IEnumerable<TElement> elements;

    public IEnumerator<TElement> GetEnumerator() => this.elements.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

Upvotes: 0

Tim Schmelter
Tim Schmelter

Reputation: 460098

Assuming that the first column is something like a unique Id:

var resultantList =  sigList
    .GroupBy(s => s.SignalGroup == "" ? s.Id.ToString() : s.SignalGroup)
    .Select(grp => grp.ToList())
    .ToList();

So if SignalGroup is an empty string the GroupBy takes the (unique) Id, in all other cases the SignalGroup, so you get the desired result of one group per Id if SignalGroup is "".

If it's not unique use Guid.NewGuid().ToString() as key for the group.

Upvotes: 8

Related Questions