按日期将两个列表合并为一个
Posted
技术标签:
【中文标题】按日期将两个列表合并为一个【英文标题】:Merging two lists into one by dates 【发布时间】:2019-04-30 10:17:43 【问题描述】:我有两个插槽列表
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;
Start 和 End 是时间块,将除以服务持续时间(服务持续时间是 int 表示分钟)。
所以我有 2 个列表(用于 2 个不同的服务),我需要按开始和结束日期将它们合并到第三个列表(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 扩展?
【问题讨论】:
由于您的程序有效,请考虑将其发布到codereview.stackexchange.com 【参考方案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();
对于您的示例,这是控制台输出:
【讨论】:
非常感谢您的努力,它看起来不错,我现在将对其进行测试,我们将看看它是如何工作的! 这段代码通过了单元测试。我不得不说我印象深刻,这正是我所需要的,谢谢!现在我只需要了解它是如何工作的,并学习如何在这个级别上编码...... 很高兴能为您提供帮助。要了解它是如何工作的,最好拿一张纸、一支铅笔,画出一些代表时间范围的线,并行地逐步执行代码并在图中查看发生了什么。【参考方案2】:您可以为您的 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;
如果两个列表中的同一个槽可能有相同的服务,您可以为您的服务类创建一个类似的比较器并使用另一个联合操作。
【讨论】:
此解决方案仅在插槽相等时才有帮助。但是我需要解决许多其他情况,例如列表 1 中的插槽包含列表 2 中的插槽时。(slot1.start slot2.end @garret,现在我发现我误解了您的问题,很抱歉尝试根据我的期望编辑您的帖子。我想对于所有可能的情况都不会有一个简单而优雅的解决方案,因为您可以有重叠和包含句点。但这确实是一个很好的问题。以上是关于按日期将两个列表合并为一个的主要内容,如果未能解决你的问题,请参考以下文章