Search code examples
c#linqgroup-by

C# Grouping by calculated values isn't working


Why isn't this grouping correctly? I'm grouping by day of week and day of month, and I end up with four results, not three.

query.GroupBy(datapoint =>
{
    var timeDimensionValue = (DateTime)datapoint.Dimensions[TimeDimension];
    var keyValues = groupingFunctions.Select(func => func(timeDimensionValue));

    return keyValues.ToArray(); // Ex: { "Monday", 15 };
});

Data:

DateTime(2023, 7, 17, 15, 0, 0)

DateTime(2023, 7, 19, 7, 0, 0) // same

DateTime(2023, 7, 19, 7, 0, 0) // same

DateTime(2023, 7, 21, 11, 30, 0)


Solution

  • Grouping by an array is not what you want, because arrays don't override Equals and GetHashCode. You want to group by the values the array contains. Since you said in a comment that this array is actually a IList<object> and dynamic, so can contain anything, you need a custom comparer which can handle that:

    public class ObjectListComparer: IEqualityComparer<IList<object>>
    {
        public bool Equals(IList<object> x, IList<object> y)
        {
            if(x == null && y == null) return true;
            if(x == null || y == null) return false;
            return x.SequenceEqual(y);
        }
    
        public int GetHashCode(IList<object> obj)
        {
            return string.Join(",", obj?.Select(obj => obj) ?? Enumerable.Empty<object>()).GetHashCode();
        }
    }
    

    Now you could use this comparer in GroupBy and many other LINQ methods:

    var groups = data.GroupBy(x => {
        var timeDimensionValue = (DateTime)datapoint.Dimensions[TimeDimension];
        List<object> keyValues = groupingFunctions.Select(func => func(timeDimensionValue));
        return keyValues;
    }, new ObjectListComparer());
    

    Since it's a comparer for IList<object> you can use it for your List<object> but also for object[].