Thank you to @GoodNightNerdPride and @NetMage for their help.
This is the version I came up with. I cheated and used a for-loop. It is less elegant but I do find it easier to read.
Below is the main extention method (which borrows from @NetMage) and beneath that is an Xunit file I used to test the whole thing.
Extention Method
public static List<Response> SumBasedUponOverlappingDates(this List<Request> requests) {
var requestsWithGroupedIds = requests
.GroupBy(x => x.EndDate)
.Select(x => new {
Ids = x.Select(y => y.Id).ToArray(),
StartDate = x.First().StartDate,
EndDate = x.Key,
Quantity = x.Sum(y => y.Quantity)
})
.OrderBy(x => x.EndDate)
.ToList();
var responses = new List<Response>();
for (int i = 0; i < requestsWithGroupedIds.Count(); i++) {
var req = requestsWithGroupedIds[i];
var overlappingEntries = requestsWithGroupedIds
.Where(x => x.StartDate <= req.StartDate && x.EndDate >= req.EndDate)
.ToList();
var resp = new Response {
Ids = overlappingEntries.SelectMany(x => x.Ids.Select(y => y)).OrderBy(x => x).ToArray(),
Quantity = overlappingEntries.Sum(x => x.Quantity),
StartDate = (i == 0) ? req.StartDate : requestsWithGroupedIds[i - 1].EndDate.AddDays(1),
EndDate = req.EndDate
};
responses.Add(resp);
}
return responses;
}
XUnit Code
using System;
using System.Linq;
using Xunit;
using System.Collections.Generic;
namespace StackOverflow.Tests {
public class GroupingAndAggregationTests {
[Fact]
void ShouldAggregateDuplicateDataIntoSingle() {
var requests = new List<Request>() {
new Request{Id = 9, StartDate = new DateTime(2021, 2, 20), EndDate = new DateTime(2021,3, 5), Quantity = 6 },
new Request{Id = 345, StartDate = new DateTime(2021, 2, 20), EndDate = new DateTime(2021,3, 5), Quantity = 29 }
};
var responses = requests.SumBasedUponOverlappingDates();
Assert.Equal(1, responses.Count());
var expectedResponse =
new Response() {
StartDate = new DateTime(2021, 2, 20),
EndDate = new DateTime(2021, 3, 5),
Quantity = 35,
Ids = new int[] { 9, 345 }
};
var actualResponse = responses[0];
Assert.True(actualResponse.IsEqual(expectedResponse));
}
[Fact]
void ShouldAggregateMultipleBasedOnOverlappingDates() {
var requests = new List<Request>() {
new Request{Id = 1, StartDate = new DateTime(2021,3,10), EndDate = new DateTime(2021,3, 21), Quantity = 1 },
new Request{Id = 2, StartDate = new DateTime(2021,3,10), EndDate = new DateTime(2021,3, 21), Quantity = 1 },
new Request{Id = 3, StartDate = new DateTime(2021,3,10), EndDate = new DateTime(2021,3, 23), Quantity = 2 },
new Request{Id = 4, StartDate = new DateTime(2021,3,10), EndDate = new DateTime(2021,3, 25), Quantity = 1 }
};
var responses = requests.SumBasedUponOverlappingDates();
Assert.Equal(3, responses.Count());
var expecedResponse1 =
new Response() {
StartDate = new DateTime(2021, 3, 10),
EndDate = new DateTime(2021, 3, 21),
Quantity = 5,
Ids = new int[] { 1, 2, 3, 4 }
};
var actualResponse1 = responses[0];
Assert.True(actualResponse1.IsEqual(expecedResponse1));
var expectedResponse2 =
new Response() {
StartDate = new DateTime(2021, 3, 22),
EndDate = new DateTime(2021, 3, 23),
Quantity = 3,
Ids = new int[] { 3, 4 }
};
var actualResponse2 = responses[1];
Assert.True(actualResponse2.IsEqual(expectedResponse2));
var expectedResponse3 =
new Response() {
StartDate = new DateTime(2021, 3, 24),
EndDate = new DateTime(2021, 3, 25),
Quantity = 1,
Ids = new int[] { 4 }
};
var actualResponse3 = responses[2];
Assert.True(actualResponse3.IsEqual(expectedResponse3));
}
}
public class Request {
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Quantity { get; set; }
}
public class Response {
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Quantity { get; set; }
public int[] Ids { get; set; }
public bool IsEqual(Response resp)
=>
StartDate == resp.StartDate &&
EndDate == resp.EndDate &&
Quantity == resp.Quantity &&
Ids.OrderBy(x => x).SequenceEqual(resp.Ids.OrderBy(x => x));
}
public static class ExtentionMethods {
public static List<Response> SumBasedUponOverlappingDates(this List<Request> requests) {
var requestsWithGroupedIds = requests
.GroupBy(x => x.EndDate)
.Select(x => new {
Ids = x.Select(y => y.Id).ToArray(),
StartDate = x.First().StartDate,
EndDate = x.Key,
Quantity = x.Sum(y => y.Quantity)
})
.OrderBy(x => x.EndDate)
.ToList();
var responses = new List<Response>();
for (int i = 0; i < requestsWithGroupedIds.Count(); i++) {
var req = requestsWithGroupedIds[i];
var overlappingEntries = requestsWithGroupedIds
.Where(x => x.StartDate <= req.StartDate && x.EndDate >= req.EndDate)
.ToList();
var resp = new Response {
Ids = overlappingEntries.SelectMany(x => x.Ids.Select(y => y)).OrderBy(x => x).ToArray(),
Quantity = overlappingEntries.Sum(x => x.Quantity),
StartDate = (i == 0) ? req.StartDate : requestsWithGroupedIds[i - 1].EndDate.AddDays(1),
EndDate = req.EndDate
};
responses.Add(resp);
}
return responses;
}
}
}