按日期将两个列表合并为一个

时间:2018-11-28 13:29:55

标签: c# linq

我有两个插槽列表

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扩展名?

2 个答案:

答案 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();

在您的示例中,这是控制台输出:

ConsoleOutput