-1

I have a list of items that need to be grouped on 3 fields. However, I need to include items in a group if some of these fields are null, but the rest match. It's kind of hard to explain, but here is a scenario.

So for example, we have a list of the following:

Items:
Amount  Type    Code   
50      Test    AA
50      Test2   BB
null    Test    AA
45      Test3   CC
50      Test    null
50      Test    AB

So we would get 4 groups

Group 1: 50/null + Test + AA/null
Group 2: 50 + Test2 + BB
Group 3: 45 + Test3 + CC
Group 4: 50 + Test + AB

We include the null rows in Group 1 because though we had null values, the rest was a match. I'm not sure how I can accomplish this via linq or C# and could use some help!

NetMage
  • 26,163
  • 3
  • 34
  • 55
wingthor
  • 54
  • 1
  • 3

1 Answers1

0

It took me a few re-reads to see what you meant. But, I think you mean to group by each of the list object's properties, while ignoring null values in the property compare.

So here's a solution by using a custom equality comparer (called ItemComparer in the code sample below) that meets your special requirement. You can then plug this comparer into the linq GroupBy function and voila:

void Main()
{
    var items = GetItems();

    foreach (var grp in items.GroupBy(i => i, new ItemComparer()))
    {
        foreach (var g in grp)
        {
            Console.WriteLine($"Grouping {grp.Key.Code}: {g.Amount?.ToString() ?? "null"} + {g.Type ?? "null"} + {g.Code?? "null"}");
        }   
        Console.WriteLine("--------------------------------------------------");
    }   
}

public class ItemComparer : IEqualityComparer<Item>
{
    // Here we consider a null on either side of the compare to be true as per your 
    // requirements. Normally a null compare returns: false.
    public bool Equals(Item a, Item b)
    {
        return 
            (a.Amount == null || b.Amount == null || a.Amount == b.Amount) &&
            (a.Type == null || b.Type == null || a.Type == b.Type) &&
            (a.Code == null || b.Code == null || a.Code == b.Code);
    }
    
    // The linq operators will try a hash code comparison first, if they are the same, 
    // then it will use the Equals function. So always returning 0 will force it to use 
    // the Equals method.
    public int GetHashCode(Item item)
    {
        return 0;       
    }
}

public List<Item> GetItems()
{
    return new List<Item>
    {
        new Item { Amount = 50, Type = "Test", Code = "AA" },
        new Item { Amount = 50, Type = "Test2", Code = "BB" },
        new Item { Amount = null, Type = "Test", Code = "AA" },
        new Item { Amount = 45, Type = "Test3", Code = "CC" },
        new Item { Amount = 50, Type = "Test", Code = null },
        new Item { Amount = 50, Type = "Test", Code = "AB" }
    };
}

public class Item 
{
    public int? Amount { get; set; }
    public string Type { get; set; }
    public string Code { get; set; }
}

This returns the following:

Grouping AA: 50 + Test + AA
Grouping AA: null + Test + AA
Grouping AA: 50 + Test + null
--------------------------------------------------
Grouping BB: 50 + Test2 + BB
--------------------------------------------------
Grouping CC: 45 + Test3 + CC
--------------------------------------------------
Grouping AB: 50 + Test + AB
--------------------------------------------------
Carlo Bos
  • 3,105
  • 2
  • 16
  • 29