David
David

Reputation: 135

Finding the remaining time from one list that isn't in another

I have been banging my head against a brick wall for a while with this one! I am close but I can't quite get the desired result and was hoping someone might be able to tell me where I am going wrong.

I have 2 lists of objects which contain a start and end time.

List A

12/03/2019 04:13:50 - 12/03/2019 06:28:52
12/03/2019 06:31:06 - 12/03/2019 06:32:09
12/03/2019 06:33:11 - 12/03/2019 06:34:48
12/03/2019 06:35:26 - 12/03/2019 06:39:52
12/03/2019 06:42:33 - 12/03/2019 08:19:31
12/03/2019 08:30:03 - 12/03/2019 08:31:07
12/03/2019 08:36:56 - 12/03/2019 09:16:31
12/03/2019 09:17:17 - 12/03/2019 10:00:00

List B

12/03/2019 06:25:35 - 12/03/2019 06:28:52
12/03/2019 06:45:23 - 12/03/2019 06:52:29
12/03/2019 06:57:43 - 12/03/2019 06:58:05
12/03/2019 06:59:46 - 12/03/2019 07:07:58
12/03/2019 07:11:09 - 12/03/2019 07:21:36
12/03/2019 07:33:10 - 12/03/2019 07:38:13
12/03/2019 07:39:54 - 12/03/2019 07:43:27
12/03/2019 07:44:01 - 12/03/2019 07:45:41
12/03/2019 07:49:59 - 12/03/2019 08:02:13
12/03/2019 08:03:31 - 12/03/2019 08:12:51
12/03/2019 08:17:09 - 12/03/2019 08:19:31
12/03/2019 08:42:04 - 12/03/2019 08:47:13
12/03/2019 09:51:37 - 12/03/2019 10:00:00 

I would like to produce a new list of start and end time objects from list a that are not in list B.

Desired Result

12/03/2019 04:13:50 - 12/03/2019 06:25:35
12/03/2019 06:31:06 - 12/03/2019 06:32:09
12/03/2019 06:33:11 - 12/03/2019 06:34:48
12/03/2019 06:35:26 - 12/03/2019 06:39:52
12/03/2019 06:42:33 - 12/03/2019 06:45:23
12/03/2019 06:52:29 - 12/03/2019 06:57:43
12/03/2019 06:58:05 - 12/03/2019 06:59:46
12/03/2019 07:07:58 - 12/03/2019 07:11:09
12/03/2019 07:21:36 - 12/03/2019 07:33:10
12/03/2019 07:38:13 - 12/03/2019 07:39:54
12/03/2019 07:43:27 - 12/03/2019 07:44:01
12/03/2019 07:45:41 - 12/03/2019 07:49:59
12/03/2019 08:02:13 - 12/03/2019 08:03:31
12/03/2019 08:12:51 - 12/03/2019 08:17:09
12/03/2019 08:30:03 - 12/03/2019 08:31:07
12/03/2019 08:36:56 - 12/03/2019 08:42:04
12/03/2019 08:47:13 - 12/03/2019 09:16:31
12/03/2019 09:17:17 - 12/03/2019 09:51:37

Here is the method I am using at the moment that is getting me the closest.

    public ShiftPattern GetRemainingTimesWithinShiftPattern(List<State> listB)
    {
        var shifts = new ShiftPattern();
        for (var a = 0; a < listB.Count; a++)
        {
            foreach (var shift in this) // this = listA
            {
                if (shift.IsTimeWithinShift(listB[a].StateStart) || shift.IsTimeWithinShift(listB[a].StateEnd))
                {
                    if (shift.IsTimeWithinShift(listB[a].StateStart))
                    {
                        var inoutShift = new Shift
                        {
                            StartDay = shift.StartDay,
                            StartTime = shift.StartTime,
                            EndDay = listB[a].StateStart.Date,
                            EndTime = listB[a].StartMsSinceMidnight
                        };
                        shifts.Add(inoutShift);
                    }
                    if (shift.IsTimeWithinShift(listB[a].StateEnd))
                    {
                        for (var c = a + 1; c < listB.Count; c++)
                        {
                            var inoutShift = new Shift();
                            inoutShift.StartDay = listB[a].StateEnd.Date;
                            inoutShift.StartTime = listB[a].EndMsSinceMidnight;
                            if (shift.IsTimeWithinShift(listB[c].StateStart))
                            {
                                inoutShift.EndDay = listB[c].StateStart.Date;
                                inoutShift.EndTime = listB[c].StartMsSinceMidnight;
                                shifts.Add(inoutShift);
                            }
                            else if (shift.EndTime > listB[a].EndMsSinceMidnight) // this is so we don't get a start and stop for the same time.
                            {
                                inoutShift.EndDay = shift.EndDay;
                                inoutShift.EndTime = shift.EndTime;
                                shifts.Add(inoutShift);
                                break;
                            }
                            a++;
                        }
                    }

                }
            }
        }
        return shifts;
    }

The results I am getting are

12/03/2019 04:13:50 - 12/03/2019 06:25:35
12/03/2019 06:42:33 - 12/03/2019 06:45:23
12/03/2019 06:52:29 - 12/03/2019 06:57:43
12/03/2019 06:58:05 - 12/03/2019 06:59:46
12/03/2019 07:07:58 - 12/03/2019 07:11:09
12/03/2019 07:21:36 - 12/03/2019 07:33:10
12/03/2019 07:38:13 - 12/03/2019 07:39:54
12/03/2019 07:43:27 - 12/03/2019 07:44:01
12/03/2019 07:45:41 - 12/03/2019 07:49:59
12/03/2019 08:02:13 - 12/03/2019 08:03:31
12/03/2019 08:12:51 - 12/03/2019 08:17:09
12/03/2019 09:17:17 - 12/03/2019 09:51:37

EDIT

Here are a couple of sample classes that produce lists A & B

    public class StartStop
    {
        public DateTime Start { get; set; }
        public DateTime Stop { get; set; }
    }
    public List<StartStop> GetListA()
    {
        return new List<StartStop>
        {
            new StartStop { Start = DateTime.Today.AddHours(4).AddMinutes(13).AddSeconds(50), Stop  = DateTime.Today.AddHours(6).AddMinutes(28).AddSeconds(52) },
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(31).AddSeconds(6), Stop  = DateTime.Today.AddHours(6).AddMinutes(32).AddSeconds(9) },
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(33).AddSeconds(11), Stop  = DateTime.Today.AddHours(6).AddMinutes(34).AddSeconds(48) },
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(35).AddSeconds(26), Stop  = DateTime.Today.AddHours(6).AddMinutes(39).AddSeconds(52) },
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(42).AddSeconds(33), Stop  = DateTime.Today.AddHours(8).AddMinutes(19).AddSeconds(31) },
            new StartStop { Start = DateTime.Today.AddHours(8).AddMinutes(30).AddSeconds(3), Stop  = DateTime.Today.AddHours(8).AddMinutes(31).AddSeconds(7) },
            new StartStop { Start = DateTime.Today.AddHours(8).AddMinutes(36).AddSeconds(56), Stop  = DateTime.Today.AddHours(9).AddMinutes(16).AddSeconds(31) },
            new StartStop { Start = DateTime.Today.AddHours(9).AddMinutes(17).AddSeconds(17), Stop  = DateTime.Today.AddHours(10) }
        };
    }
    public List<StartStop> GetListB()
    {
        return new List<StartStop>
        {
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(25).AddSeconds(35), Stop  = DateTime.Today.AddHours(6).AddMinutes(28).AddSeconds(52) },
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(45).AddSeconds(23), Stop  = DateTime.Today.AddHours(6).AddMinutes(52).AddSeconds(29) },
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(57).AddSeconds(43), Stop  = DateTime.Today.AddHours(6).AddMinutes(58).AddSeconds(5) },
            new StartStop { Start = DateTime.Today.AddHours(6).AddMinutes(59).AddSeconds(46), Stop  = DateTime.Today.AddHours(7).AddMinutes(7).AddSeconds(58) },
            new StartStop { Start = DateTime.Today.AddHours(7).AddMinutes(11).AddSeconds(9), Stop  = DateTime.Today.AddHours(7).AddMinutes(21).AddSeconds(36) },
            new StartStop { Start = DateTime.Today.AddHours(7).AddMinutes(33).AddSeconds(10), Stop  = DateTime.Today.AddHours(7).AddMinutes(38).AddSeconds(13) },
            new StartStop { Start = DateTime.Today.AddHours(7).AddMinutes(39).AddSeconds(54), Stop  = DateTime.Today.AddHours(7).AddMinutes(43).AddSeconds(27) },
            new StartStop { Start = DateTime.Today.AddHours(7).AddMinutes(44).AddSeconds(1), Stop  = DateTime.Today.AddHours(7).AddMinutes(45).AddSeconds(41) },
            new StartStop { Start = DateTime.Today.AddHours(7).AddMinutes(49).AddSeconds(59), Stop  = DateTime.Today.AddHours(8).AddMinutes(2).AddSeconds(13) },
            new StartStop { Start = DateTime.Today.AddHours(8).AddMinutes(3).AddSeconds(31), Stop  = DateTime.Today.AddHours(8).AddMinutes(12).AddSeconds(51) },
            new StartStop { Start = DateTime.Today.AddHours(8).AddMinutes(17).AddSeconds(9), Stop  = DateTime.Today.AddHours(8).AddMinutes(19).AddSeconds(31) },
            new StartStop { Start = DateTime.Today.AddHours(8).AddMinutes(42).AddSeconds(4), Stop  = DateTime.Today.AddHours(8).AddMinutes(47).AddSeconds(13) },
            new StartStop { Start = DateTime.Today.AddHours(9).AddMinutes(51).AddSeconds(37), Stop  = DateTime.Today.AddHours(10) },
        };
    }

Upvotes: 1

Views: 69

Answers (1)

Damien_The_Unbeliever
Damien_The_Unbeliever

Reputation: 239754

I'd break it down something like this, using LINQ. Untested since I don't need the typing practice from your sample data1:

  var listA = new List<Something>();
  var listB = new List<Something>();
  var result = new List<Something>();

  var allPeriods = listA
    .SelectMany(st => new[] {
      new { AtTime = st.StartTime, A = 1, B = 0 },
      new { AtTime = st.EndTime, A = -1, B = 0} })
    .Concat(listB.SelectMany(st => new[]
    {
      new {AtTime = st.StartTime, A = 0, B = 1},
      new {AtTime = st.EndTime, A = 0, B = -1}
    }));
  var sorted = allPeriods.OrderBy(per => per.AtTime).ToList();
  var paired = sorted.Zip(sorted.Skip(1),
       (first, second) => new { Start = first, End = second });
  var a = 0;
  var b = 0;
  foreach(var pair in paired)
  {
    a += pair.Start.A;
    b += pair.Start.B;
    if (a > 0 && b == 0)
    {
      result.Add(new Something {
        StartTime = pair.Start.AtTime,
        EndTime = pair.End.AtTime });
    }
  }

Hopefully you can see the logic I'm using here. I basically extract all of the possible start and end times, and pair them up in order. I then process through them and keep a running count of how many A's and B's are now open. If there are As open but no Bs, we output a result.

This won't merge adjoining periods but those shouldn't occur based on your sample data anyway.

Unfortunately this approach does need to take a couple of passes through the data. Unless this is a performance hotspot and you're dealing with lists containing lots of items though, I wouldn't worry about it.

The only efficient approaches to this that won't make multiple passes though at least one of the lists requires some kind of multi-dimensional range access based on both start and end dates and I can't think of anything standard in the framework that would work for this.


To some extent, this is based on how I'd solve the same problem in SQL, which can more naturally orient itself to this sort of set-based approach. Others may prefer more declarative approaches.


1If I take your sample data code and make corrections to prevent time travel, this now produces 18 results, which on spot checking match your expected results.

Upvotes: 1

Related Questions