我有两个插槽列表
public class Slot
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public List<Service> Services { get; set; }
}
public class Service
{
public int Id { get; set; }
public int Duration { get; set; }
}
public class MergingClass
{
public List<Slot> MergeSlots()
{
var mergedList = new List<Slot>();
var list1 = new List<Slot>
{
new Slot
{
Start = new DateTime(2018, 11, 1, 8, 0, 0),
End = new DateTime(2018, 11, 1, 11, 0, 0),
Services = new List<Service>
{
new Service
{
Duration = 20,
Id = 1
}
}
},
new Slot
{
Start = new DateTime(2018, 11, 1, 12, 0, 0),
End = new DateTime(2018, 11, 1, 16, 0, 0),
Services = new List<Service>
{
new Service
{
Duration = 20,
Id = 1
}
}
}
};
var list2 = new List<Slot>
{
new Slot
{
Start = new DateTime(2018, 11, 1, 8, 0, 0),
End = new DateTime(2018, 11, 1, 11, 0, 0),
Services = new List<Service>
{
new Service
{
Duration = 30,
Id = 2
}
}
},
new Slot
{
Start = new DateTime(2018, 11, 1, 12, 0, 0),
End = new DateTime(2018, 11, 1, 18, 0, 0),
Services = new List<Service>
{
new Service
{
Duration = 30,
Id = 2
}
}
}
};
return mergedList;
}
}
开始和结束是时间块,将被服务持续时间除以(服务持续时间以int表示分钟)。
所以我有2个列表(用于2个differenet服务),我需要按开始日期和结束日期将它们合并到第三个列表(mergedList)中。
在这种情况下,方法MergeSlots应该返回:
mergedList = new List<Slot>
{
new Slot
{
Start = new DateTime(2018, 11, 1, 8, 0, 0),
End = new DateTime(2018, 11, 1, 11, 0, 0),
Services = new List<Service>
{
new Service
{
Duration = 20,
Id = 1
},
new Service
{
Duration = 30,
Id = 2
}
}
},
new Slot
{
Start = new DateTime(2018, 11, 1, 12, 0, 0),
End = new DateTime(2018, 11, 1, 16, 0, 0),
Services = new List<Service>
{
new Service
{
Duration = 20,
Id = 1
},
new Service
{
Duration = 30,
Id = 2
}
}
},
new Slot
{
Start = new DateTime(2018, 11, 1, 16, 0, 0),
End = new DateTime(2018, 11, 1, 18, 0, 0),
Services = new List<Service>
{
new Service
{
Duration = 30,
Id = 2
}
}
}
};
当然,这两个插槽列表都来自我无法影响的系统,并且每次都会不同。
我试图一步一步地做,但是解决方案又庞大又丑陋,而且容易出错:
foreach (var slot in list2)
{
var slotWithStartInList1 = list1.FirstOrDefault(x => x.Start <= slot.Start && x.End > slot.Start);
if (slotWithStartInList1 != null)
{
if (slot.Start == slotWithStartInList1.Start)
{
if (slot.End == slotWithStartInList1.End)
{
slot.Services.AddRange(slotWithStartInList1.Services);
mergedList.Add(slot);
continue;
}
if (slot.End < slotWithStartInList1.End)
{
slot.Services.AddRange(slotWithStartInList1.Services);
slotWithStartInList1.Start = slot.End;
mergedList.Add(slot);
mergedList.Add(slotWithStartInList1);
continue;
}
slotWithStartInList1.Services.AddRange(slot.Services);
slot.Start = slotWithStartInList1.End;
mergedList.Add(slotWithStartInList1);
mergedList.Add(slot);
continue;
}
if (slot.End == slotWithStartInList1.End)
{
slotWithStartInList1.End = slot.Start;
slot.Services.AddRange(slotWithStartInList1.Services);
mergedList.Add(slotWithStartInList1);
mergedList.Add(slot);
continue;
}
if (slot.End > slotWithStartInList1.End)
{
var tempSlot = new Slot
{
Start = slot.Start,
End = slotWithStartInList1.End,
Services = new List<Services>()
};
tempSlot.Services.AddRange(slotWithStartInList1.Services);
tempSlot.Services.AddRange(slot.Services);
slotWithStartInList1.End = tempSlot.Start;
slot.Start = tempSlot.End;
mergedList.Add(tempSlot);
mergedList.Add(slot);
mergedList.Add(slotWithStartInList1);
continue;
}
var tempSlot2 = new Slot
{
Start = slotWithStartInList1.Start,
End = slot.Start,
Services = new List<Services>()
};
tempSlot2.Services.AddRange(slotWithStartInList1.Services);
slot.Services.AddRange(slotWithStartInList1.Services);
slotWithStartInList1.Start = slot.End;
mergedList.Add(tempSlot2);
mergedList.Add(slot);
mergedList.Add(slotWithStartInList1);
continue;
}
var slotWithEndInList1 = list1.FirstOrDefault(x => x.Start < slot.End && x.End >= slot.End);
if (slotWithEndInList1 != null)
{
if (slot.End == slotWithEndInList1.End)
{
slot.End = slotWithEndInList1.End;
slotWithEndInList1.Services.AddRange(slot.Services);
mergedList.Add(slot);
mergedList.Add(slotWithEndInList1);
continue;
}
var tempSlot2 = new Slot
{
Start = slotWithEndInList1.Start,
End = slot.End,
Services = new List<Services>()
};
tempSlot2.Services.AddRange(slotWithEndInList1.Services);
tempSlot2.Services.AddRange(slot.Services);
slot.End = tempSlot2.Start;
slotWithEndInList1.Start = tempSlot2.End;
mergedList.Add(tempSlot2);
mergedList.Add(slot);
mergedList.Add(slotWithEndInList1);
continue;
}
mergedList.Add(slot);
}
foreach (var slot in list1)
{
if (mergedList.Any(x => x.Start == slot.Start))
{
continue;
}
mergedList.Add(slot);
}
return mergedList;
我可以添加一些私有方法来避免代码重复,但是我想知道是否有更好(更简洁)的方法来实现我的目标?
也许有一些linq扩展名?
答案 0 :(得分:1)
您可以为Slot类创建一个比较器类,然后使用一些linq获得所需的结果。这是比较器:
public class SlotComparer : IEqualityComparer<Slot>
{
public bool Equals(Slot x, Slot y)
{
return x.Start.Equals(y.Start)
&& x.End.Equals(y.End);
}
public int GetHashCode(Slot slot)
{
return (slot.Start.ToLongDateString()
+ slot.End.ToLongDateString()).GetHashCode();
}
}
这是您如何应用它的方法。这就是在您的foreach循环中发生的事情。
SlotComparer sc = new SlotComparer();
var mergedList = list1
.Union(list2, sc)
.OrderBy(s => s.Start)
.ThenBy(s => s.End)
.ToList();
foreach (var distinctSlot in mergedList)
{
var slotFromList1 = list1.FirstOrDefault(s => sc.Equals(s, distinctSlot));
var slotFromList2 = list2.FirstOrDefault(s => sc.Equals(s, distinctSlot));
var services = new List<Service>();
if (slotFromList1 != null)
services.AddRange(slotFromList1.Services);
if (slotFromList2 != null)
services.AddRange(slotFromList2.Services);
distinctSlot.Services = services.OrderBy(s => s.Id).ToList();
}
return mergedList;
如果两个列表中的同一插槽可能有相同的服务,则可以为Service类创建一个类似的比较器,并使用另一个联合操作。
答案 1 :(得分:1)
好吧,我找到了另一种解决方案。可能比代码更具可读性。我创建了一个Range类,其中包含代表连续时间范围的所有时段,其中某些时段重叠。 Recalculate方法在时间跨度开始或结束的那些时间将其拆分为较小的块。对于这些小块,计算了与这些块重叠的时隙的服务。
public class Range
{
private bool _originalSlotsChanged = false;
private List<Slot> _originalSlots = new List<Slot>();
private List<Slot> _recalculatedSlots = new List<Slot>();
public Range(Slot slot)
{
AddOriginalSlot(slot);
}
public DateTime? Start { get; set; } = null;
public DateTime? End { get; set; } = null;
public List<Slot> RecalculatedSlots
{
get
{
if (_originalSlotsChanged)
Recalculate();
return _recalculatedSlots;
}
}
public void AddOriginalSlot(Slot slot)
{
if (slot != null)
{
_originalSlots.Add(slot);
if (Start == null || slot.Start < Start)
Start = slot.Start;
if (End == null || slot.End > End)
End = slot.End;
_originalSlotsChanged = true;
}
}
private void Recalculate()
{
_recalculatedSlots.Clear();
var pointsInRange = _originalSlots.Select(s => s.Start);
pointsInRange = pointsInRange.Union(
_originalSlots.Select(s => s.End)).Distinct().OrderBy(p => p);
var arr = pointsInRange.ToArray();
for (int i = 0; i < arr.Length - 1; i++)
{
Slot slot = new Slot()
{
Start = arr[i],
End = arr[i + 1]
};
AddServicesToNewSlot(slot);
_recalculatedSlots.Add(slot);
}
}
private void AddServicesToNewSlot(Slot newSlot)
{
List<Service> services = new List<Service>();
foreach (Slot originalSlot in _originalSlots)
{
if (IsNewSlotInOriginalSlot(originalSlot, newSlot))
services.AddRange(originalSlot.Services);
}
newSlot.Services = services.OrderBy(s => s.Id).ToList();
// optionally check for distinct services here
}
private bool IsNewSlotInOriginalSlot(Slot originalSlot, Slot newSlot)
{
return originalSlot.Start <= newSlot.Start && newSlot.Start < originalSlot.End;
}
}
现在,我们可以合并所有插槽,对其进行排序,在范围内进行划分,然后获得结果。
var slotList = list1.Union(list2).OrderBy(s => s.Start);
Range lastRange = null;
var rangeList = new List<Range>();
foreach (Slot slot in slotList)
{
if (lastRange == null || slot.Start >= lastRange.End.Value)
{
lastRange = new Range(slot);
rangeList.Add(lastRange);
}
else
{
lastRange.AddOriginalSlot(slot);
}
}
foreach (var range in rangeList)
foreach (var slot in range.RecalculatedSlots)
{
Console.WriteLine($"Slot {slot.Start} - {slot.End}");
foreach (Service service in slot.Services)
Console.WriteLine($" Service {service.Id}: {service.Duration}");
}
Console.ReadLine();
在您的示例中,这是控制台输出: