Вдохновленный вашим вкладом - и спасибо за это - я придумал решение. Я создаю дерево всех возможных комбинаций. Корень дерева - это первый элемент в списке объектов. Мой класс обработчика дерева вызывает себя рекурсивно, чтобы пройти через объекты. В листовом узле я проверяю пересечение периодов всех узлов в пути. Если есть пересекающийся период, у меня есть правильная комбинация, и обработчик дерева передает ее через делегата. Если путь не имеет пересекающегося периода, его нельзя использовать. Я исключил вторичные классы, но я могу вставить их, если вы запросите это:
internal delegate void PathDelivery(JPTreeNode treeNode, Period periodInCommon); //Deliver a path through this delegate
internal class TreeHandler //The main class
{
private readonly PathDelivery _pathDelivery;
// Construct with
public TreeHandler(PathDelivery pathDelivery)
{
_pathDelivery = pathDelivery;
}
public void HandleNode(IList<SourceRow> jpSource, int level,JPTreeNode parentNode, Period intersectingPeriod)
{
int sequenceId = level + 1;
IList<SourceRow> children = new List<SourceRow>(System.Linq.Enumerable.Where(jpSource, S => S.Id == sequenceId));
if (children.Count == 0) //The last leaf level... return the path throug PathDelivery delegate if valid dates
{
if (intersectingPeriod!= null)
_pathDelivery(parentNode, intersectingPeriod); //Only deliver this path if all nodes in path has an intersecting period
}
else //This node has children to add
{
foreach (SourceRow child in children) //Loop and create children
{
Stop stop = new Stop(child.Id, child.Period, child.Description); //Value object for child node
//Re-calculate the valid period (intersect) for the tree path
Period newIntersect = Period.Intersect(stop.Period, intersectingPeriod);
JPTreeNode childNode = new JPTreeNode(parentNode, stop); //Create the child node
if (parentNode!= null) //If not at root level, add child node to parent node
parentNode.ChildNodes.Add(childNode);
// Recursive call, handle possible grandchildren (children of this childNode) or finish path
HandleNode(jpSource, sequenceId, childNode, newIntersect);
}
}
}
}
Ниже приведено устройство для модульного тестирования, демонстрирующее использование:
[TestFixture]
public sealed class TestRecursiveDataHandling
{
private IList<Path> _pathList;
[SetUp]
public void SetUp()
{
_pathList = new List<Path>();
}
[TearDown]
public void TearDown()
{
_pathList = null;
}
[Test]
public void CreateLists()
{
//Create the data source
IList<SourceRow> jpSource = new List<SourceRow>();
jpSource.Add(new SourceRow("1", 1, new DateTime(2000, 1, 1), new DateTime(2009, 12, 31)));
jpSource.Add(new SourceRow("2A", 2, new DateTime(2000, 1, 1), new DateTime(2004, 12, 31)));
jpSource.Add(new SourceRow("2B", 2, new DateTime(2005, 1, 1), new DateTime(2009, 12, 31)));
jpSource.Add(new SourceRow("3", 3, new DateTime(2000, 1, 1), new DateTime(2009, 12, 31)));
jpSource.Add(new SourceRow("4A", 4, new DateTime(2000, 1, 1), new DateTime(2008, 12, 31)));
jpSource.Add(new SourceRow("4B", 4, new DateTime(2009, 1, 1), new DateTime(2009, 12, 31)));
//Instantiate handler
TreeHandler handler = new TreeHandler(CreatePathFromButtonNode);
//Handle root node, and recurse
handler.HandleNode(jpSource, 0, null, Period.Infinite);
//DISPLAY TREE PATHS IN CONSOLE
DisplayResultInConsole();
//A simple assertion. Expect that 3 paths were created.
Assert.That(_pathList.Count, Is.EqualTo(3));
}
private void DisplayResultInConsole()
{
for (int i = 0; i < _pathList.Count; i++ )
{
IList<Stop> jpList = _pathList[i].Stops;
string stopList = string.Empty;
foreach (Stop stop in jpList)
{
stopList = stopList + stop.ID + ";" + stop.Description + ";"+ stop.Period + " \n";
}
string p = "List " + i + ">> Period " + _pathList[i].Period;
Console.WriteLine("List " + i + ">> Period " + p);
Console.WriteLine("Stops: ");
Console.WriteLine(stopList);
Console.WriteLine();
}
}
//Method for receiving paths from leaf nodes. Passed as delegate to TreeHandler
private void CreatePathFromButtonNode(JPTreeNode treeNode, Period periodInCommon)
{
IList<Stop> stopList = new List<Stop>();
JPTreeNode currentNode = treeNode;
while (currentNode!= null)
{
stopList.Add(currentNode.NodeValue);
currentNode = currentNode.ParentNode;
}
_pathList.Add(new Path(periodInCommon, stopList));
}
}
Этот тест генерирует следующий вывод на консоль. Обратите внимание, что путь (комбинация) 4B; 3; 2A; 1 не был возвращен. Это потому, что периоды 2A и 4B не пересекаются:
Список 0 >> Список периодов 0 >> Период С: 01.01.2000 До: 12/31/2004
Остановки:
4; 4A; От: 01.01.2000 Кому: 12/31/2008
3; 3; От: 01.01.2000 До: 12/31/2009
2; 2A; От: 01.01.2000 Кому: 12/31/2004
1; 1; От: 01.01.2000 Кому: 31.12.2009
Список 1 >> Период Список 1 >> Период С: 01.01.2005 До: 12/31/2008
Остановки:
4; 4A; От: 01.01.2000 Кому: 12/31/2008
3; 3; От: 01.01.2000 Кому: 12/31/2009
2; 2B; От: 01.01.2005 До: 12/31/2009
1; 1; От: 01.01.2000 Кому: 31.12.2009
Список 2 >> Список периодов 2 >> Период С: 01.01.2009 До: 31.12.2009
Остановки:
4; 4B; От: 01.01.2009 До: 12/31/2009
3; 3; От: 01.01.2000 До: 12/31/2009
2; 2B; От: 01.01.2005 До: 12/31/2009
1; 1; От: 01.01.2000 Кому: 31.12.2009