【问题标题】: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 扩展?

【问题讨论】:

标签: c# linq


【解决方案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,现在我发现我误解了您的问题,很抱歉尝试根据我的期望编辑您的帖子。我想对于所有可能的情况都不会有一个简单而优雅的解决方案,因为您可以有重叠和包含句点。但这确实是一个很好的问题。
猜你喜欢
  • 1970-01-01
  • 2015-06-03
  • 1970-01-01
  • 2012-08-19
  • 2015-09-08
  • 2021-12-16
  • 1970-01-01
  • 2014-01-27
相关资源
最近更新 更多