From 702eef11e48c4d055b6e04efe38270780906f507 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Tue, 16 Jun 2020 18:10:05 +0000 Subject: [PATCH 01/17] added tests --- .../com/google/sps/FindMeetingQueryTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java index edd435b..52bda37 100644 --- a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java +++ b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java @@ -35,6 +35,7 @@ public final class FindMeetingQueryTest { private static final String PERSON_A = "Person A"; private static final String PERSON_B = "Person B"; private static final String PERSON_C = "Person C"; + private static final String PERSON_C = "Person D"; // All dates are the first day of the year 2020. private static final int TIME_0800AM = TimeRange.getTimeInMinutes(8, 0); @@ -44,6 +45,7 @@ public final class FindMeetingQueryTest { private static final int TIME_1000AM = TimeRange.getTimeInMinutes(10, 0); private static final int TIME_1100AM = TimeRange.getTimeInMinutes(11, 00); private static final int TIME_1230PM = TimeRange.getTimeInMinutes(12, 30); + private static final int TIME_0600PM = TimeRange.getTimeInMinutes(18, 0); private static final int DURATION_15_MINUTES = 15; private static final int DURATION_30_MINUTES = 30; @@ -492,4 +494,94 @@ public void onlyOptionalAndImpossible() { Collection expected = Arrays.asList(); Assert.assertEquals(expected, actual); } + + @Test + public void onlyOptionalOneLeftOut() { + // Three optional attendees A, B, C. One gap in which A, B can attend, another in which B, C can attend, another in which only C can attend. The former two should be returned. + // + // Events : |--A--| |-----A--------| + // |--B--| |---B---| + // |--------C-----| + // Day : |---------------------------| + // Options : |-----| |----| + + Collection events = + Arrays.asList( + new Event( + "Event 1", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + Arrays.asList(PERSON_A)), + new Event( + "Event 2", + TimeRange.fromStartEnd(TIME_1100AM, TimeRange.END_OF_DAY, true), + Arrays.asList(PERSON_A)), + new Event( + "Event 3", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + Arrays.asList(PERSON_B)), + new Event( + "Event 4", + TimeRange.fromStartEnd(TIME_0600PM, TimeRange.END_OF_DAY, true), + Arrays.asList(PERSON_B)), + new Event( + "Event 5", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY,TIME_1230PM , true), + Arrays.asList(PERSON_C))); + + MeetingRequest request = new MeetingRequest(Arrays.asList(), DURATION_30_MINUTES); + request.addOptionalAttendee(PERSON_A); + request.addOptionalAttendee(PERSON_B); + request.addOptionalAttendee(PERSON_C); + + Collection actual = query.query(events, request); + Collection expected = Arrays.asList( + TimeRange.fromStartEnd(TIME_0900AM, TIME_1100AM, false), + TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM, false)); + Assert.assertEquals(expected, actual); + } + + @Test + public void bothTypesOneLeftOut() { + // Three optional attendees A, B, C. One mandatory attendee D. + // One gap in which A, B, D can attend, another in which only C, D can attend. The former should be returned + // + // Events : |--D--| |---D---| + // |---A----| + // |---B----| + // |-C--| + // Day : |---------------------------| + // Options : |----| + + Collection events = + Arrays.asList( + new Event( + "Event 1", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + Arrays.asList(PERSON_D)), + new Event( + "Event 2", + TimeRange.fromStartEnd(TIME_0600PM, TimeRange.END_OF_DAY, true), + Arrays.asList(PERSON_D)), + new Event( + "Event 3", + TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), + Arrays.asList(PERSON_A)), + new Event( + "Event 4", + TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), + Arrays.asList(PERSON_B)), + new Event( + "Event 5", + TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM), + Arrays.asList(PERSON_C))); + + MeetingRequest request = new MeetingRequest(Arrays.asList(PERSON_D), DURATION_30_MINUTES); + request.addOptionalAttendee(PERSON_A); + request.addOptionalAttendee(PERSON_B); + request.addOptionalAttendee(PERSON_C); + + Collection actual = query.query(events, request); + Collection expected = Arrays.asList(TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM, false)); + Assert.assertEquals(expected, actual); + } } From 462c5ef69e31d8c301e50ce6cab34cd50d2322ec Mon Sep 17 00:00:00 2001 From: Claire Y Date: Wed, 17 Jun 2020 17:42:24 +0000 Subject: [PATCH 02/17] doesn't work --- .../java/com/google/sps/FindMeetingQuery.java | 90 +++++++++++++++---- .../com/google/sps/FindMeetingQueryTest.java | 40 ++++----- 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 2f19ec6..2bf917e 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -20,12 +20,16 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; /** Lists possible meeting times based on meeting information it takes in. */ public final class FindMeetingQuery { private static final int END_OF_DAY = TimeRange.getTimeInMinutes(23, 59); + // Todo: remove this and adjust. private static final Comparator ORDER_BY_START_ASC = new Comparator() { @@ -36,32 +40,86 @@ public int compare(Event a, Event b) { }; /** - * Returns a list of time periods in which the meeting, specified by request, could happen. If one - * or more time slots exists so that both mandatory and optional attendees can attend, it returns - * those time slots. Otherwise, it returns the time slots that fit just the mandatory attendees. + * Returns a list of time periods in which the meeting, specified by request, could happen. If no + * time exists for all optional and mandatory attendees, find the time slot(s) that allow + * mandatory attendees and the greatest possible number of optional attendees to attend. * * @param eventsCollection the events we know about * @param request information about the meeting, including attendees, optional attendees, and how * long it needs to be */ public Collection query(Collection eventsCollection, MeetingRequest request) { - Collection withOptionalAttendees = getMeetingTimes(eventsCollection, request, true); + ArrayList mandatoryAttendeesMeetingTimes = + getMeetingTimes(eventsCollection, request); + HashMap> optionalAttendeesFreeTimes = + getFreeTimes(eventsCollection, request); + return optimalMeetingTimes(mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes); + // System.out.println(); + // return mandatoryAttendeesMeetingTimes; + } + + private Collection optimalMeetingTimes(ArrayList windows, HashMap> optional){ + ArrayList res = new ArrayList(); + TreeMap sweep = new TreeMap(); + for(Map.Entry e : optional.entrySet()){ + ArrayList value = e.getValue(); + for(TimeRange t:value){ + sweep.put(t.start(),sweep.getOrDefault(t.start(),0)+1); + sweep.put(t.end(),sweep.getOrDefault(t.end(),0)-1); + } + } + int sum=0,i=0; + for(Map.Entry e : sweep.entrySet()){ + sum+=e.getValue(); + + } + return res; + } - // Special case: if no mandatory attendees and optional attendees' schedules cannot fit in a - // meeting, no meeting times are possible. - return !withOptionalAttendees.isEmpty() || request.getAttendees().isEmpty() - ? withOptionalAttendees - : getMeetingTimes(eventsCollection, request, false); + private HashMap> getFreeTimes( + Collection eventsCollection, MeetingRequest request) { + HashMap> busy = new HashMap>(); + HashSet attendees = new HashSet(request.getOptionalAttendees()); + for (Event event : eventsCollection) { + for (String attendee : event.getAttendees()) { + busy.putIfAbsent(attendee, new ArrayList()); + busy.get(attendee).add(event.getWhen()); + } + } + for (Map.Entry e : busy.entrySet()) { + String key = (String) e.getKey(); + + ArrayList value = getComplement((ArrayList) e.getValue()); + System.out.print(key + " : "); + for (TimeRange t : value) { + System.out.print(t.toString() + " "); + } + System.out.println(); + e.setValue(value); + } + + return busy; } - private Collection getMeetingTimes( - Collection eventsCollection, - MeetingRequest request, - boolean includeOptionalAttendees) { - HashSet attendees = new HashSet(request.getAttendees()); - if (includeOptionalAttendees) { - attendees.addAll(request.getOptionalAttendees()); + private ArrayList getComplement(ArrayList times) { + Collections.sort(times, TimeRange.ORDER_BY_START); + ArrayList res = new ArrayList(); + int end = TimeRange.START_OF_DAY; + for (TimeRange t : times) { + if (end < t.start()) { + res.add(TimeRange.fromStartEnd(end, t.start(), false)); + } + end = t.end(); } + if (end <= END_OF_DAY) { + res.add(TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true)); + } + return res; + } + + private ArrayList getMeetingTimes( + Collection eventsCollection, MeetingRequest request) { + HashSet attendees = new HashSet(request.getAttendees()); ArrayList events = getRelevantEvents(attendees, new ArrayList(eventsCollection)); List possibleMeetingTimes = new ArrayList(); diff --git a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java index 52bda37..48ddd5a 100644 --- a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java +++ b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java @@ -35,7 +35,7 @@ public final class FindMeetingQueryTest { private static final String PERSON_A = "Person A"; private static final String PERSON_B = "Person B"; private static final String PERSON_C = "Person C"; - private static final String PERSON_C = "Person D"; + private static final String PERSON_D = "Person D"; // All dates are the first day of the year 2020. private static final int TIME_0800AM = TimeRange.getTimeInMinutes(8, 0); @@ -494,10 +494,11 @@ public void onlyOptionalAndImpossible() { Collection expected = Arrays.asList(); Assert.assertEquals(expected, actual); } - + @Test public void onlyOptionalOneLeftOut() { - // Three optional attendees A, B, C. One gap in which A, B can attend, another in which B, C can attend, another in which only C can attend. The former two should be returned. + // Three optional attendees A, B, C. One gap in which A, B can attend, another in which B, C can + // attend, another in which only C can attend. The former two should be returned. // // Events : |--A--| |-----A--------| // |--B--| |---B---| @@ -510,22 +511,18 @@ public void onlyOptionalOneLeftOut() { new Event( "Event 1", TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), - Arrays.asList(PERSON_A)), + Arrays.asList(PERSON_A, PERSON_B)), new Event( "Event 2", - TimeRange.fromStartEnd(TIME_1100AM, TimeRange.END_OF_DAY, true), - Arrays.asList(PERSON_A)), - new Event( - "Event 3", TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), Arrays.asList(PERSON_B)), new Event( - "Event 4", + "Event 3", TimeRange.fromStartEnd(TIME_0600PM, TimeRange.END_OF_DAY, true), Arrays.asList(PERSON_B)), new Event( - "Event 5", - TimeRange.fromStartEnd(TimeRange.START_OF_DAY,TIME_1230PM , true), + "Event 4", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_1230PM, true), Arrays.asList(PERSON_C))); MeetingRequest request = new MeetingRequest(Arrays.asList(), DURATION_30_MINUTES); @@ -534,16 +531,18 @@ public void onlyOptionalOneLeftOut() { request.addOptionalAttendee(PERSON_C); Collection actual = query.query(events, request); - Collection expected = Arrays.asList( + Collection expected = + Arrays.asList( TimeRange.fromStartEnd(TIME_0900AM, TIME_1100AM, false), TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM, false)); Assert.assertEquals(expected, actual); } - + @Test public void bothTypesOneLeftOut() { - // Three optional attendees A, B, C. One mandatory attendee D. - // One gap in which A, B, D can attend, another in which only C, D can attend. The former should be returned + // Three optional attendees A, B, C. One mandatory attendee D. + // One gap in which A, B, D can attend, another in which only C, D can attend. The former should + // be returned // // Events : |--D--| |---D---| // |---A----| @@ -565,14 +564,10 @@ public void bothTypesOneLeftOut() { new Event( "Event 3", TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), - Arrays.asList(PERSON_A)), + Arrays.asList(PERSON_A, PERSON_B)), new Event( "Event 4", - TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), - Arrays.asList(PERSON_B)), - new Event( - "Event 5", - TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM), + TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM, false), Arrays.asList(PERSON_C))); MeetingRequest request = new MeetingRequest(Arrays.asList(PERSON_D), DURATION_30_MINUTES); @@ -581,7 +576,8 @@ public void bothTypesOneLeftOut() { request.addOptionalAttendee(PERSON_C); Collection actual = query.query(events, request); - Collection expected = Arrays.asList(TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM, false)); + Collection expected = + Arrays.asList(TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM, false)); Assert.assertEquals(expected, actual); } } From faac3cc862876ba370bf536229a9f382408bbff0 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Wed, 17 Jun 2020 19:43:49 +0000 Subject: [PATCH 03/17] passes all tests but unreadable --- .../java/com/google/sps/FindMeetingQuery.java | 86 +++++++++++++------ .../com/google/sps/FindMeetingQueryTest.java | 20 +++-- 2 files changed, 75 insertions(+), 31 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 2bf917e..4bde615 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -51,53 +51,90 @@ public int compare(Event a, Event b) { public Collection query(Collection eventsCollection, MeetingRequest request) { ArrayList mandatoryAttendeesMeetingTimes = getMeetingTimes(eventsCollection, request); + System.out.println("window: "); + for (TimeRange t : mandatoryAttendeesMeetingTimes) { + System.out.println(t.toString()); + } HashMap> optionalAttendeesFreeTimes = getFreeTimes(eventsCollection, request); - return optimalMeetingTimes(mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes); - // System.out.println(); - // return mandatoryAttendeesMeetingTimes; + return optimalMeetingTimes( + mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes, request.getDuration()); } - - private Collection optimalMeetingTimes(ArrayList windows, HashMap> optional){ + + private Collection optimalMeetingTimes( + ArrayList windows, HashMap> optional, long duration) { + if (optional.isEmpty()) return windows; ArrayList res = new ArrayList(); TreeMap sweep = new TreeMap(); - for(Map.Entry e : optional.entrySet()){ - ArrayList value = e.getValue(); - for(TimeRange t:value){ - sweep.put(t.start(),sweep.getOrDefault(t.start(),0)+1); - sweep.put(t.end(),sweep.getOrDefault(t.end(),0)-1); + for (Map.Entry e : optional.entrySet()) { + ArrayList value = (ArrayList) e.getValue(); + System.out.println(e.getKey() + " optionals:"); + for (TimeRange t : value) { + sweep.put(t.start(), sweep.getOrDefault(t.start(), 0) + 1); + sweep.put(t.end(), sweep.getOrDefault(t.end(), 0) - 1); + System.out.println(t.toString()); + } + } + System.out.println(); + int sum = 0, prev = 0, j = 0, best = 0; // j= + for (Map.Entry e : sweep.entrySet()) { + if (prev >= windows.get(j).end()) j++; // need to move onto next window + if (j >= windows.size()) break; + TimeRange next = + TimeRange.fromStartEnd( + Math.max(windows.get(j).start(), prev), + Math.min(windows.get(j).end(), (Integer) e.getKey()), + false); + if (next.duration() >= duration) { + if (sum > best) { + best = sum; + res.clear(); + System.out.println("cleared"); + } + if (sum == best) res.add(next); + System.out.println("adding " + next.toString()); } + sum += (Integer) e.getValue(); // for next window + prev = (Integer) e.getKey(); } - int sum=0,i=0; - for(Map.Entry e : sweep.entrySet()){ - sum+=e.getValue(); - + if (j >= windows.size()) { + return res.isEmpty() ? windows : res; } - return res; + TimeRange next = + TimeRange.fromStartEnd( + Math.max(windows.get(j).start(), prev), + Math.min(windows.get(j).end(), TimeRange.END_OF_DAY), + true); + if (next.duration() >= duration) { + if (sum > best) { + best = sum; + res.clear(); + } + if (sum == best) res.add(next); + } + return res.isEmpty() ? windows : res; } + // gets free times for optional attendees private HashMap> getFreeTimes( Collection eventsCollection, MeetingRequest request) { HashMap> busy = new HashMap>(); HashSet attendees = new HashSet(request.getOptionalAttendees()); for (Event event : eventsCollection) { - for (String attendee : event.getAttendees()) { + for (String attendee : attendees) { + if (!event.getAttendees().contains(attendee)) { // attendee is not going to this event + continue; + } busy.putIfAbsent(attendee, new ArrayList()); busy.get(attendee).add(event.getWhen()); } } for (Map.Entry e : busy.entrySet()) { String key = (String) e.getKey(); - ArrayList value = getComplement((ArrayList) e.getValue()); - System.out.print(key + " : "); - for (TimeRange t : value) { - System.out.print(t.toString() + " "); - } - System.out.println(); e.setValue(value); } - + busy.entrySet().removeIf(e -> e.getValue().isEmpty()); return busy; } @@ -114,6 +151,7 @@ private ArrayList getComplement(ArrayList times) { if (end <= END_OF_DAY) { res.add(TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true)); } + return res; } @@ -121,7 +159,7 @@ private ArrayList getMeetingTimes( Collection eventsCollection, MeetingRequest request) { HashSet attendees = new HashSet(request.getAttendees()); ArrayList events = getRelevantEvents(attendees, new ArrayList(eventsCollection)); - List possibleMeetingTimes = new ArrayList(); + ArrayList possibleMeetingTimes = new ArrayList(); // Need to check this so we don't access out of bounds when we add first gap. if (events.isEmpty()) { diff --git a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java index 48ddd5a..02ac63d 100644 --- a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java +++ b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java @@ -356,7 +356,8 @@ public void optionalAttendeeNotScheduled() { @Test public void optionalAttendeeRestrictsMeetingTimes() { - // Meeting should scheduled with an optional person C, thus invalidating a meeting time that was + // Meeting should be scheduled with an optional person C, thus invalidating a meeting time that + // was // valid without person C. // // Events : |--A--||--C--||--B--| @@ -463,8 +464,9 @@ public void onlyOptionalAndPossible() { } @Test - public void onlyOptionalAndImpossible() { - // Two optional attendees with no gaps in their schedules. We should see no gaps returned. + public void onlyOptionalAndDisjoint() { + // Two optional attendees with schedules that perfectly split the day. We should see three + // sections returned. // // Events : |--A--| |---A--------| // |---B----| @@ -491,7 +493,11 @@ public void onlyOptionalAndImpossible() { request.addOptionalAttendee(PERSON_B); Collection actual = query.query(events, request); - Collection expected = Arrays.asList(); + Collection expected = + Arrays.asList( + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0930AM, false), + TimeRange.fromStartDuration(TIME_0930AM, DURATION_90_MINUTES), + TimeRange.fromStartEnd(TIME_1100AM, TimeRange.END_OF_DAY, true)); Assert.assertEquals(expected, actual); } @@ -514,15 +520,15 @@ public void onlyOptionalOneLeftOut() { Arrays.asList(PERSON_A, PERSON_B)), new Event( "Event 2", - TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), - Arrays.asList(PERSON_B)), + TimeRange.fromStartEnd(TIME_1100AM, TimeRange.END_OF_DAY, true), + Arrays.asList(PERSON_A)), new Event( "Event 3", TimeRange.fromStartEnd(TIME_0600PM, TimeRange.END_OF_DAY, true), Arrays.asList(PERSON_B)), new Event( "Event 4", - TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_1230PM, true), + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_1230PM, false), Arrays.asList(PERSON_C))); MeetingRequest request = new MeetingRequest(Arrays.asList(), DURATION_30_MINUTES); From 137830a3b0303c42160e1c0cd2adb84b8aa759bd Mon Sep 17 00:00:00 2001 From: Claire Y Date: Wed, 17 Jun 2020 20:00:03 +0000 Subject: [PATCH 04/17] documentation part 1 --- .../java/com/google/sps/FindMeetingQuery.java | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 4bde615..03483e5 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -50,13 +50,9 @@ public int compare(Event a, Event b) { */ public Collection query(Collection eventsCollection, MeetingRequest request) { ArrayList mandatoryAttendeesMeetingTimes = - getMeetingTimes(eventsCollection, request); - System.out.println("window: "); - for (TimeRange t : mandatoryAttendeesMeetingTimes) { - System.out.println(t.toString()); - } + getMeetingsSatisfyingAllAttendees(eventsCollection, request); HashMap> optionalAttendeesFreeTimes = - getFreeTimes(eventsCollection, request); + getFreeTimes(eventsCollection, new HashSet(request.getOptionalAttendees())); return optimalMeetingTimes( mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes, request.getDuration()); } @@ -115,47 +111,63 @@ private Collection optimalMeetingTimes( return res.isEmpty() ? windows : res; } - // gets free times for optional attendees + /** + * Returns a mapping of each attendee to the time intervals they are free in a day. + * + * @param eventsCollection all events to be considered. + * @param attendees all attendees to be considered + */ private HashMap> getFreeTimes( - Collection eventsCollection, MeetingRequest request) { - HashMap> busy = new HashMap>(); - HashSet attendees = new HashSet(request.getOptionalAttendees()); + Collection eventsCollection, HashSet attendees) { + HashMap> times = new HashMap>(); for (Event event : eventsCollection) { for (String attendee : attendees) { - if (!event.getAttendees().contains(attendee)) { // attendee is not going to this event + + // attendee is not going to this event + if (!event.getAttendees().contains(attendee)) { continue; } - busy.putIfAbsent(attendee, new ArrayList()); - busy.get(attendee).add(event.getWhen()); + times.putIfAbsent(attendee, new ArrayList()); + times.get(attendee).add(event.getWhen()); } } - for (Map.Entry e : busy.entrySet()) { - String key = (String) e.getKey(); + for (Map.Entry e : times.entrySet()) { ArrayList value = getComplement((ArrayList) e.getValue()); + + // To save space, we change the range of times to its complement instead of creating a new + // HashMap. e.setValue(value); } - busy.entrySet().removeIf(e -> e.getValue().isEmpty()); - return busy; + + // Remove attendees that have no free time (if they were busy the whole day). + times.entrySet().removeIf(e -> e.getValue().isEmpty()); + return times; } + /** + * Returns all times that don't overlap with given times in a day. + * + * @param times non-overlapping intervals of times in a 24-hour day + */ private ArrayList getComplement(ArrayList times) { Collections.sort(times, TimeRange.ORDER_BY_START); ArrayList res = new ArrayList(); + + // end tracks the end of the last time period, and thus the beginning of the next one. int end = TimeRange.START_OF_DAY; - for (TimeRange t : times) { - if (end < t.start()) { - res.add(TimeRange.fromStartEnd(end, t.start(), false)); + for (TimeRange time : times) { + if (end < time.start()) { + res.add(TimeRange.fromStartEnd(end, time.start(), false)); } - end = t.end(); + end = time.end(); } if (end <= END_OF_DAY) { res.add(TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true)); } - return res; } - private ArrayList getMeetingTimes( + private ArrayList getMeetingsSatisfyingAllAttendees( Collection eventsCollection, MeetingRequest request) { HashSet attendees = new HashSet(request.getAttendees()); ArrayList events = getRelevantEvents(attendees, new ArrayList(eventsCollection)); From 07709abffa4b331dea31bde65b5e90527b53b493 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Wed, 17 Jun 2020 20:31:32 +0000 Subject: [PATCH 05/17] extracted out functions from getOptimalMeetingTimes --- .../java/com/google/sps/FindMeetingQuery.java | 134 ++++++++++++------ 1 file changed, 88 insertions(+), 46 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 03483e5..043f366 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -53,62 +53,104 @@ public Collection query(Collection eventsCollection, MeetingRe getMeetingsSatisfyingAllAttendees(eventsCollection, request); HashMap> optionalAttendeesFreeTimes = getFreeTimes(eventsCollection, new HashSet(request.getOptionalAttendees())); - return optimalMeetingTimes( + return getOptimalMeetingTimes( mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes, request.getDuration()); } - private Collection optimalMeetingTimes( - ArrayList windows, HashMap> optional, long duration) { - if (optional.isEmpty()) return windows; + /** + * Returns all meeting times that allow the most optional attendees to attend. These times must + * also fall in a good window and must be at least minTime long. Utilizes a sweep-line algorithm. + * + * @param goodWindows all times in which a meeting can be scheduled + * @param optional all optional attendees to be considered + * @param minTime the minimum time a meeting must last for + */ + private Collection getOptimalMeetingTimes( + ArrayList goodWindows, + HashMap> optionalAttendeesFreeTimes, + long minTime) { + + // If there are no optional attendees free times, return all good windows. + if (optionalAttendeesFreeTimes.isEmpty()) return goodWindows; + ArrayList res = new ArrayList(); - TreeMap sweep = new TreeMap(); - for (Map.Entry e : optional.entrySet()) { - ArrayList value = (ArrayList) e.getValue(); - System.out.println(e.getKey() + " optionals:"); - for (TimeRange t : value) { - sweep.put(t.start(), sweep.getOrDefault(t.start(), 0) + 1); - sweep.put(t.end(), sweep.getOrDefault(t.end(), 0) - 1); - System.out.println(t.toString()); - } - } - System.out.println(); - int sum = 0, prev = 0, j = 0, best = 0; // j= + TreeMap sweep = getChanges(optionalAttendeesFreeTimes); + + int sum = 0, prev = 0, j = 0, best = 0; for (Map.Entry e : sweep.entrySet()) { - if (prev >= windows.get(j).end()) j++; // need to move onto next window - if (j >= windows.size()) break; - TimeRange next = - TimeRange.fromStartEnd( - Math.max(windows.get(j).start(), prev), - Math.min(windows.get(j).end(), (Integer) e.getKey()), - false); - if (next.duration() >= duration) { - if (sum > best) { - best = sum; - res.clear(); - System.out.println("cleared"); - } - if (sum == best) res.add(next); - System.out.println("adding " + next.toString()); - } + if (prev >= goodWindows.get(j).end()) j++; // need to move onto next window + if (j >= goodWindows.size()) break; + + best = + updateOptimalTimes( + TimeRange.fromStartEnd( + Math.max(goodWindows.get(j).start(), prev), + Math.min(goodWindows.get(j).end(), (Integer) e.getKey()), + false), + best, + sum, + res, + minTime); sum += (Integer) e.getValue(); // for next window prev = (Integer) e.getKey(); } - if (j >= windows.size()) { - return res.isEmpty() ? windows : res; + if (j >= goodWindows.size()) { + return res.isEmpty() ? goodWindows : res; } - TimeRange next = - TimeRange.fromStartEnd( - Math.max(windows.get(j).start(), prev), - Math.min(windows.get(j).end(), TimeRange.END_OF_DAY), - true); - if (next.duration() >= duration) { - if (sum > best) { - best = sum; - res.clear(); + best = + updateOptimalTimes( + TimeRange.fromStartEnd( + Math.max(goodWindows.get(j).start(), prev), + Math.min(goodWindows.get(j).end(), TimeRange.END_OF_DAY), + true), + best, + sum, + res, + minTime); + + return res.isEmpty() ? goodWindows : res; + } + + /** + * Updates optimalMeetingTimes based on current time range's attendance. + * + * @param optionalAttendeesFreeTimes all attendees and their free times + */ + private int updateOptimalTimes( + TimeRange time, + int bestAttendance, + int currAttendance, + ArrayList optimalMeetingTimes, + long minTime) { + if (time.duration() >= minTime) { + // Clear out all former optimal meeting times. They aren't the most optimal anymore. + if (currAttendance > bestAttendance) { + bestAttendance = currAttendance; + optimalMeetingTimes.clear(); + } + if (currAttendance == bestAttendance) { + optimalMeetingTimes.add(time); + } + } + return bestAttendance; + } + + /** + * Returns a change log of how many attendees are available in at a certain time. + * + * @param optionalAttendeesFreeTimes all attendees and their free times + */ + private TreeMap getChanges( + HashMap> optionalAttendeesFreeTimes) { + TreeMap changes = new TreeMap(); + for (Map.Entry e : optionalAttendeesFreeTimes.entrySet()) { + ArrayList freeTimes = (ArrayList) e.getValue(); + for (TimeRange time : freeTimes) { + changes.put(time.start(), changes.getOrDefault(time.start(), 0) + 1); + changes.put(time.end(), changes.getOrDefault(time.end(), 0) - 1); } - if (sum == best) res.add(next); } - return res.isEmpty() ? windows : res; + return changes; } /** @@ -134,7 +176,7 @@ private HashMap> getFreeTimes( for (Map.Entry e : times.entrySet()) { ArrayList value = getComplement((ArrayList) e.getValue()); - // To save space, we change the range of times to its complement instead of creating a new + // To save space, we change the range of times to its complement instead of adding to a new // HashMap. e.setValue(value); } From 0173049a065a1e0b0ac1130a536869ec23749199 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Wed, 17 Jun 2020 20:51:55 +0000 Subject: [PATCH 06/17] docs mostly done, just add a few more @param's --- .../java/com/google/sps/FindMeetingQuery.java | 100 +++++++++++------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 043f366..c0cd4a1 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -50,16 +50,17 @@ public int compare(Event a, Event b) { */ public Collection query(Collection eventsCollection, MeetingRequest request) { ArrayList mandatoryAttendeesMeetingTimes = - getMeetingsSatisfyingAllAttendees(eventsCollection, request); + getMeetingsSatisfyingAllAttendees(eventsCollection, new HashSet(request.getAttendees())); HashMap> optionalAttendeesFreeTimes = - getFreeTimes(eventsCollection, new HashSet(request.getOptionalAttendees())); + getOptionalAttendeesFreeTimes(eventsCollection, new HashSet(request.getOptionalAttendees())); return getOptimalMeetingTimes( mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes, request.getDuration()); } /** * Returns all meeting times that allow the most optional attendees to attend. These times must - * also fall in a good window and must be at least minTime long. Utilizes a sweep-line algorithm. + * also fall in a good window and must be at least minTime long. Utilizes two pointers: one to + * goodWindows, one to changeLog. * * @param goodWindows all times in which a meeting can be scheduled * @param optional all optional attendees to be considered @@ -71,50 +72,67 @@ private Collection getOptimalMeetingTimes( long minTime) { // If there are no optional attendees free times, return all good windows. - if (optionalAttendeesFreeTimes.isEmpty()) return goodWindows; + if (optionalAttendeesFreeTimes.isEmpty()){ + return goodWindows; + } - ArrayList res = new ArrayList(); - TreeMap sweep = getChanges(optionalAttendeesFreeTimes); + ArrayList optimalMeetingTimes = new ArrayList(); + TreeMap changeLog = getChanges(optionalAttendeesFreeTimes); + int currAttendance = 0, bestAttendance = 0,prevTime = 0, currentGoodWindow = 0; + + for (Map.Entry e : changeLog.entrySet()) { + + // The time spanned from prevTime to the current time before it is ahead of the current good + // window. Move onto next window. + if (prevTime >= goodWindows.get(currentGoodWindow).end()) { + currentGoodWindow++; + } - int sum = 0, prev = 0, j = 0, best = 0; - for (Map.Entry e : sweep.entrySet()) { - if (prev >= goodWindows.get(j).end()) j++; // need to move onto next window - if (j >= goodWindows.size()) break; + // All good windows have been considered. + if (currentGoodWindow >= goodWindows.size()) { + break; + } - best = + bestAttendance = updateOptimalTimes( TimeRange.fromStartEnd( - Math.max(goodWindows.get(j).start(), prev), - Math.min(goodWindows.get(j).end(), (Integer) e.getKey()), + Math.max(goodWindows.get(currentGoodWindow).start(), prevTime), + Math.min(goodWindows.get(currentGoodWindow).end(), (Integer) e.getKey()), false), - best, - sum, - res, + bestAttendance, + currAttendance, + optimalMeetingTimes, minTime); - sum += (Integer) e.getValue(); // for next window - prev = (Integer) e.getKey(); + + currAttendance += (Integer) e.getValue(); + prevTime = (Integer) e.getKey(); } - if (j >= goodWindows.size()) { - return res.isEmpty() ? goodWindows : res; + + // Do this so we don't access out of bounds later. + if (currentGoodWindow >= goodWindows.size()) { + return optimalMeetingTimes.isEmpty() ? goodWindows : optimalMeetingTimes; } - best = + + bestAttendance = updateOptimalTimes( TimeRange.fromStartEnd( - Math.max(goodWindows.get(j).start(), prev), - Math.min(goodWindows.get(j).end(), TimeRange.END_OF_DAY), + Math.max(goodWindows.get(currentGoodWindow).start(), prevTime), + Math.min(goodWindows.get(currentGoodWindow).end(), TimeRange.END_OF_DAY), true), - best, - sum, - res, + bestAttendance, + currAttendance, + optimalMeetingTimes, minTime); - return res.isEmpty() ? goodWindows : res; + // If there are no meeting times with at least one optional attendee, just return the good windows. + return optimalMeetingTimes.isEmpty() ? goodWindows : optimalMeetingTimes; } /** - * Updates optimalMeetingTimes based on current time range's attendance. + * Updates optimalMeetingTimes based on current meeting time's attendance. * * @param optionalAttendeesFreeTimes all attendees and their free times + TODO */ private int updateOptimalTimes( TimeRange time, @@ -123,6 +141,7 @@ private int updateOptimalTimes( ArrayList optimalMeetingTimes, long minTime) { if (time.duration() >= minTime) { + // Clear out all former optimal meeting times. They aren't the most optimal anymore. if (currAttendance > bestAttendance) { bestAttendance = currAttendance; @@ -136,9 +155,10 @@ private int updateOptimalTimes( } /** - * Returns a change log of how many attendees are available in at a certain time. + * Returns a change log of how many optional attendees are available in at a certain time. Uses sweep-line + * algorithm. * - * @param optionalAttendeesFreeTimes all attendees and their free times + * @param optionalAttendeesFreeTimes mapping of all attendees to their free times */ private TreeMap getChanges( HashMap> optionalAttendeesFreeTimes) { @@ -154,16 +174,16 @@ private TreeMap getChanges( } /** - * Returns a mapping of each attendee to the time intervals they are free in a day. + * Returns a mapping of each optional attendee to the time intervals they are free in a day. * * @param eventsCollection all events to be considered. - * @param attendees all attendees to be considered + * @param optionalAttendees all optional attendees to be considered */ - private HashMap> getFreeTimes( - Collection eventsCollection, HashSet attendees) { + private HashMap> getOptionalAttendeesFreeTimes( + Collection eventsCollection, HashSet optionalAttendees) { HashMap> times = new HashMap>(); for (Event event : eventsCollection) { - for (String attendee : attendees) { + for (String attendee : optionalAttendees) { // attendee is not going to this event if (!event.getAttendees().contains(attendee)) { @@ -187,7 +207,7 @@ private HashMap> getFreeTimes( } /** - * Returns all times that don't overlap with given times in a day. + * Returns all times that don't overlap with the given times in a day. * * @param times non-overlapping intervals of times in a 24-hour day */ @@ -209,9 +229,13 @@ private ArrayList getComplement(ArrayList times) { return res; } +/** + * TODO + * + * @param + */ private ArrayList getMeetingsSatisfyingAllAttendees( - Collection eventsCollection, MeetingRequest request) { - HashSet attendees = new HashSet(request.getAttendees()); + Collection eventsCollection, HashSet attendees) { ArrayList events = getRelevantEvents(attendees, new ArrayList(eventsCollection)); ArrayList possibleMeetingTimes = new ArrayList(); From 4ca8972b23a7bc676f289414183ef97b3530d5d1 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Wed, 17 Jun 2020 21:14:54 +0000 Subject: [PATCH 07/17] docs done --- .../java/com/google/sps/FindMeetingQuery.java | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index c0cd4a1..54af419 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -26,10 +26,8 @@ import java.util.Set; import java.util.TreeMap; -/** Lists possible meeting times based on meeting information it takes in. */ +/** Lists optimal meeting times based on meeting information it takes in. */ public final class FindMeetingQuery { - private static final int END_OF_DAY = TimeRange.getTimeInMinutes(23, 59); - // Todo: remove this and adjust. private static final Comparator ORDER_BY_START_ASC = new Comparator() { @@ -44,15 +42,15 @@ public int compare(Event a, Event b) { * time exists for all optional and mandatory attendees, find the time slot(s) that allow * mandatory attendees and the greatest possible number of optional attendees to attend. * - * @param eventsCollection the events we know about + * @param events the events to be considered * @param request information about the meeting, including attendees, optional attendees, and how * long it needs to be */ - public Collection query(Collection eventsCollection, MeetingRequest request) { + public Collection query(Collection events, MeetingRequest request) { ArrayList mandatoryAttendeesMeetingTimes = - getMeetingsSatisfyingAllAttendees(eventsCollection, new HashSet(request.getAttendees())); + getMandatoryAttendeesMeetingTimes(events, request); HashMap> optionalAttendeesFreeTimes = - getOptionalAttendeesFreeTimes(eventsCollection, new HashSet(request.getOptionalAttendees())); + getOptionalAttendeesFreeTimes(events, new HashSet(request.getOptionalAttendees())); return getOptimalMeetingTimes( mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes, request.getDuration()); } @@ -72,16 +70,16 @@ private Collection getOptimalMeetingTimes( long minTime) { // If there are no optional attendees free times, return all good windows. - if (optionalAttendeesFreeTimes.isEmpty()){ + if (optionalAttendeesFreeTimes.isEmpty()) { return goodWindows; } ArrayList optimalMeetingTimes = new ArrayList(); TreeMap changeLog = getChanges(optionalAttendeesFreeTimes); - int currAttendance = 0, bestAttendance = 0,prevTime = 0, currentGoodWindow = 0; - + int currAttendance = 0, bestAttendance = 0, prevTime = 0, currentGoodWindow = 0; + for (Map.Entry e : changeLog.entrySet()) { - + // The time spanned from prevTime to the current time before it is ahead of the current good // window. Move onto next window. if (prevTime >= goodWindows.get(currentGoodWindow).end()) { @@ -124,15 +122,19 @@ private Collection getOptimalMeetingTimes( optimalMeetingTimes, minTime); - // If there are no meeting times with at least one optional attendee, just return the good windows. + // If there are no meeting times with at least one optional attendee, just return the good + // windows. return optimalMeetingTimes.isEmpty() ? goodWindows : optimalMeetingTimes; } /** * Updates optimalMeetingTimes based on current meeting time's attendance. * + * @param time current meeting time range + * @param bestAttendance best attendance of optional attendees seen so far + * @param currAttendance attendance optional attendees of current meeting * @param optionalAttendeesFreeTimes all attendees and their free times - TODO + * @param minTime minimum meeting time */ private int updateOptimalTimes( TimeRange time, @@ -141,7 +143,7 @@ private int updateOptimalTimes( ArrayList optimalMeetingTimes, long minTime) { if (time.duration() >= minTime) { - + // Clear out all former optimal meeting times. They aren't the most optimal anymore. if (currAttendance > bestAttendance) { bestAttendance = currAttendance; @@ -155,8 +157,8 @@ private int updateOptimalTimes( } /** - * Returns a change log of how many optional attendees are available in at a certain time. Uses sweep-line - * algorithm. + * Returns a change log of how many optional attendees are available in at a certain time. Uses + * sweep-line algorithm. * * @param optionalAttendeesFreeTimes mapping of all attendees to their free times */ @@ -176,13 +178,13 @@ private TreeMap getChanges( /** * Returns a mapping of each optional attendee to the time intervals they are free in a day. * - * @param eventsCollection all events to be considered. + * @param events all events to be considered. * @param optionalAttendees all optional attendees to be considered */ private HashMap> getOptionalAttendeesFreeTimes( - Collection eventsCollection, HashSet optionalAttendees) { + Collection events, HashSet optionalAttendees) { HashMap> times = new HashMap>(); - for (Event event : eventsCollection) { + for (Event event : events) { for (String attendee : optionalAttendees) { // attendee is not going to this event @@ -223,37 +225,41 @@ private ArrayList getComplement(ArrayList times) { } end = time.end(); } - if (end <= END_OF_DAY) { + if (end <= TimeRange.END_OF_DAY) { res.add(TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true)); } return res; } -/** - * TODO + /** + * Returns all meeting times that satisfy all mandatory attendees of request. * - * @param + * @param events all events to be considered + * @param request information about the meeting, including attendees, optional attendees, and how + * long it needs to be */ - private ArrayList getMeetingsSatisfyingAllAttendees( - Collection eventsCollection, HashSet attendees) { - ArrayList events = getRelevantEvents(attendees, new ArrayList(eventsCollection)); + private ArrayList getMandatoryAttendeesMeetingTimes( + Collection events, MeetingRequest request) { + ArrayList relevantEvents = + getRelevantEvents( + new HashSet(request.getAttendees()), new ArrayList(events)); ArrayList possibleMeetingTimes = new ArrayList(); // Need to check this so we don't access out of bounds when we add first gap. - if (events.isEmpty()) { + if (relevantEvents.isEmpty()) { addIfLongEnough( - TimeRange.fromStartEnd(0, END_OF_DAY, true), possibleMeetingTimes, request.getDuration()); + TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), possibleMeetingTimes, request.getDuration()); return possibleMeetingTimes; } - Collections.sort(events, ORDER_BY_START_ASC); + Collections.sort(relevantEvents, ORDER_BY_START_ASC); // Add first gap. addIfLongEnough( - TimeRange.fromStartEnd(0, events.get(0).getWhen().start(), false), + TimeRange.fromStartEnd(0, relevantEvents.get(0).getWhen().start(), false), possibleMeetingTimes, request.getDuration()); - int end = events.get(0).getWhen().end(); - for (Event event : events) { + int end = relevantEvents.get(0).getWhen().end(); + for (Event event : relevantEvents) { // event can be merged with current time range if (event.getWhen().start() <= end) { end = Math.max(end, event.getWhen().end()); @@ -269,7 +275,7 @@ private ArrayList getMeetingsSatisfyingAllAttendees( // Add the last one we were tracking. addIfLongEnough( - TimeRange.fromStartEnd(end, END_OF_DAY, true), possibleMeetingTimes, request.getDuration()); + TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), possibleMeetingTimes, request.getDuration()); return possibleMeetingTimes; } From a46fdd863ad98380e47c2f61c5e0e61340bf6aff Mon Sep 17 00:00:00 2001 From: Claire Y Date: Thu, 18 Jun 2020 16:44:59 +0000 Subject: [PATCH 08/17] refactored code to shorten it, passes all tests --- .../java/com/google/sps/FindMeetingQuery.java | 201 +++++++----------- .../com/google/sps/FindMeetingQueryTest.java | 4 +- 2 files changed, 77 insertions(+), 128 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 54af419..1f25ffd 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -21,6 +21,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -49,10 +50,12 @@ public int compare(Event a, Event b) { public Collection query(Collection events, MeetingRequest request) { ArrayList mandatoryAttendeesMeetingTimes = getMandatoryAttendeesMeetingTimes(events, request); - HashMap> optionalAttendeesFreeTimes = - getOptionalAttendeesFreeTimes(events, new HashSet(request.getOptionalAttendees())); + if (mandatoryAttendeesMeetingTimes.isEmpty()) return mandatoryAttendeesMeetingTimes; + return getOptimalMeetingTimes( - mandatoryAttendeesMeetingTimes, optionalAttendeesFreeTimes, request.getDuration()); + mandatoryAttendeesMeetingTimes, + getChanges(events, new HashSet(request.getOptionalAttendees())), + request.getDuration()); } /** @@ -65,66 +68,80 @@ public Collection query(Collection events, MeetingRequest requ * @param minTime the minimum time a meeting must last for */ private Collection getOptimalMeetingTimes( - ArrayList goodWindows, - HashMap> optionalAttendeesFreeTimes, - long minTime) { - + ArrayList goodWindows, TreeMap changeLog, long minTime) { + System.out.println("changelog size=" + changeLog.size()); // If there are no optional attendees free times, return all good windows. - if (optionalAttendeesFreeTimes.isEmpty()) { + if (changeLog.isEmpty()) { return goodWindows; } ArrayList optimalMeetingTimes = new ArrayList(); - TreeMap changeLog = getChanges(optionalAttendeesFreeTimes); - int currAttendance = 0, bestAttendance = 0, prevTime = 0, currentGoodWindow = 0; - - for (Map.Entry e : changeLog.entrySet()) { + // TreeMap changeLog = getChanges(optionalAttendeesFreeTimes); + int currAttendance = 0, bestAttendance = 0, prevTime = 0; + Set> entrySet = + (Set>) changeLog.entrySet(); - // The time spanned from prevTime to the current time before it is ahead of the current good - // window. Move onto next window. - if (prevTime >= goodWindows.get(currentGoodWindow).end()) { - currentGoodWindow++; - } - - // All good windows have been considered. - if (currentGoodWindow >= goodWindows.size()) { - break; + List> c = new ArrayList>(entrySet); + for (int j = 0, i = 0; i < c.size(); ++i) { + int t = (Integer) c.get(i).getKey(); + j = Math.max(0, j - 1); + while (j < goodWindows.size() && goodWindows.get(j).start() < t) { + bestAttendance = + updateOptimalTimes( + TimeRange.fromStartEnd( + Math.max(goodWindows.get(j).start(), prevTime), + Math.min(goodWindows.get(j).end(), (Integer) c.get(i).getKey()), + false), + bestAttendance, + currAttendance, + optimalMeetingTimes, + minTime); + j++; } + prevTime = (Integer) c.get(i).getKey(); + currAttendance += (Integer) c.get(i).getValue(); + } - bestAttendance = - updateOptimalTimes( - TimeRange.fromStartEnd( - Math.max(goodWindows.get(currentGoodWindow).start(), prevTime), - Math.min(goodWindows.get(currentGoodWindow).end(), (Integer) e.getKey()), - false), - bestAttendance, - currAttendance, - optimalMeetingTimes, - minTime); + // If there are no meeting times with at least one optional attendee, just return the good + // windows. + return optimalMeetingTimes.isEmpty() ? goodWindows : optimalMeetingTimes; + } - currAttendance += (Integer) e.getValue(); - prevTime = (Integer) e.getKey(); + /** + * Returns a change log of how many optional attendees are available in at a certain time. Uses + * sweep-line algorithm. + * + * @param optionalAttendeesFreeTimes mapping of all attendees to their free times + */ + private TreeMap getChanges( + Collection events, HashSet optionalAttendees) { + TreeMap changes = new TreeMap(); + if (optionalAttendees.isEmpty()) return changes; + changes.put( + TimeRange.START_OF_DAY, + optionalAttendees.size()); // assume everyone is free at the start of the day + changes.put(TimeRange.END_OF_DAY + 1, -optionalAttendees.size()); + for (Event event : events) { + int count = 0; + for (String attendee : optionalAttendees) { + if (event.getAttendees().contains(attendee)) { + count++; + } + } + if (count == 0) { + continue; + } + System.out.println(event.getWhen().toString() + "," + count); + int s = event.getWhen().start(), e = event.getWhen().end(); + changes.put(s, changes.getOrDefault(s, 0) - count); + changes.put(e, changes.getOrDefault(e, 0) + count); } - // Do this so we don't access out of bounds later. - if (currentGoodWindow >= goodWindows.size()) { - return optimalMeetingTimes.isEmpty() ? goodWindows : optimalMeetingTimes; + for (Map.Entry e : changes.entrySet()) { + System.out.println((Integer) e.getKey() + ": " + (Integer) e.getValue()); } - bestAttendance = - updateOptimalTimes( - TimeRange.fromStartEnd( - Math.max(goodWindows.get(currentGoodWindow).start(), prevTime), - Math.min(goodWindows.get(currentGoodWindow).end(), TimeRange.END_OF_DAY), - true), - bestAttendance, - currAttendance, - optimalMeetingTimes, - minTime); - - // If there are no meeting times with at least one optional attendee, just return the good - // windows. - return optimalMeetingTimes.isEmpty() ? goodWindows : optimalMeetingTimes; + return changes; } /** @@ -142,6 +159,7 @@ private int updateOptimalTimes( int currAttendance, ArrayList optimalMeetingTimes, long minTime) { + System.out.println(bestAttendance + " vs. " + currAttendance + "," + time.duration()); if (time.duration() >= minTime) { // Clear out all former optimal meeting times. They aren't the most optimal anymore. @@ -156,81 +174,6 @@ private int updateOptimalTimes( return bestAttendance; } - /** - * Returns a change log of how many optional attendees are available in at a certain time. Uses - * sweep-line algorithm. - * - * @param optionalAttendeesFreeTimes mapping of all attendees to their free times - */ - private TreeMap getChanges( - HashMap> optionalAttendeesFreeTimes) { - TreeMap changes = new TreeMap(); - for (Map.Entry e : optionalAttendeesFreeTimes.entrySet()) { - ArrayList freeTimes = (ArrayList) e.getValue(); - for (TimeRange time : freeTimes) { - changes.put(time.start(), changes.getOrDefault(time.start(), 0) + 1); - changes.put(time.end(), changes.getOrDefault(time.end(), 0) - 1); - } - } - return changes; - } - - /** - * Returns a mapping of each optional attendee to the time intervals they are free in a day. - * - * @param events all events to be considered. - * @param optionalAttendees all optional attendees to be considered - */ - private HashMap> getOptionalAttendeesFreeTimes( - Collection events, HashSet optionalAttendees) { - HashMap> times = new HashMap>(); - for (Event event : events) { - for (String attendee : optionalAttendees) { - - // attendee is not going to this event - if (!event.getAttendees().contains(attendee)) { - continue; - } - times.putIfAbsent(attendee, new ArrayList()); - times.get(attendee).add(event.getWhen()); - } - } - for (Map.Entry e : times.entrySet()) { - ArrayList value = getComplement((ArrayList) e.getValue()); - - // To save space, we change the range of times to its complement instead of adding to a new - // HashMap. - e.setValue(value); - } - - // Remove attendees that have no free time (if they were busy the whole day). - times.entrySet().removeIf(e -> e.getValue().isEmpty()); - return times; - } - - /** - * Returns all times that don't overlap with the given times in a day. - * - * @param times non-overlapping intervals of times in a 24-hour day - */ - private ArrayList getComplement(ArrayList times) { - Collections.sort(times, TimeRange.ORDER_BY_START); - ArrayList res = new ArrayList(); - - // end tracks the end of the last time period, and thus the beginning of the next one. - int end = TimeRange.START_OF_DAY; - for (TimeRange time : times) { - if (end < time.start()) { - res.add(TimeRange.fromStartEnd(end, time.start(), false)); - } - end = time.end(); - } - if (end <= TimeRange.END_OF_DAY) { - res.add(TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true)); - } - return res; - } - /** * Returns all meeting times that satisfy all mandatory attendees of request. * @@ -248,7 +191,9 @@ private ArrayList getMandatoryAttendeesMeetingTimes( // Need to check this so we don't access out of bounds when we add first gap. if (relevantEvents.isEmpty()) { addIfLongEnough( - TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), possibleMeetingTimes, request.getDuration()); + TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), + possibleMeetingTimes, + request.getDuration()); return possibleMeetingTimes; } Collections.sort(relevantEvents, ORDER_BY_START_ASC); @@ -275,7 +220,9 @@ private ArrayList getMandatoryAttendeesMeetingTimes( // Add the last one we were tracking. addIfLongEnough( - TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), possibleMeetingTimes, request.getDuration()); + TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), + possibleMeetingTimes, + request.getDuration()); return possibleMeetingTimes; } diff --git a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java index 02ac63d..5d6b419 100644 --- a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java +++ b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java @@ -548,7 +548,7 @@ public void onlyOptionalOneLeftOut() { public void bothTypesOneLeftOut() { // Three optional attendees A, B, C. One mandatory attendee D. // One gap in which A, B, D can attend, another in which only C, D can attend. The former should - // be returned + // be returned. // // Events : |--D--| |---D---| // |---A----| @@ -587,3 +587,5 @@ public void bothTypesOneLeftOut() { Assert.assertEquals(expected, actual); } } + +// TODO: add more tests for start/ end of days, overlapping, nested, double booked From ed72b389431ffffa23e6f03f76c7f1ce032c49ad Mon Sep 17 00:00:00 2001 From: Claire Y Date: Thu, 18 Jun 2020 17:06:47 +0000 Subject: [PATCH 09/17] solution 2 fails double booking test... must revise --- .../com/google/sps/FindMeetingQueryTest.java | 113 +++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java index 5d6b419..635b409 100644 --- a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java +++ b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java @@ -470,7 +470,7 @@ public void onlyOptionalAndDisjoint() { // // Events : |--A--| |---A--------| // |---B----| - // Day : |---------------------------| + // Day : |----||-------||------------| // Options : Collection events = @@ -586,6 +586,113 @@ public void bothTypesOneLeftOut() { Arrays.asList(TimeRange.fromStartEnd(TIME_1230PM, TIME_0600PM, false)); Assert.assertEquals(expected, actual); } -} -// TODO: add more tests for start/ end of days, overlapping, nested, double booked + @Test + public void optimalIsSplitDay() { + // Two optional attendees A, B. One mandatory attendee C. + // Only the immeidate start and immediate end should be returned. + // + // Events : |---C---| + // |-------A------| |-B--| + // + // Day : |---------------------------| + // Options : |--------------| |----| + + Collection events = + Arrays.asList( + new Event( + "Event 1", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + Arrays.asList(PERSON_A)), + new Event( + "Event 2", + TimeRange.fromStartEnd(TIME_1230PM, TimeRange.END_OF_DAY, true), + Arrays.asList(PERSON_B)), + new Event( + "Event 3", + TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), + Arrays.asList(PERSON_C))); + + MeetingRequest request = new MeetingRequest(Arrays.asList(PERSON_C), DURATION_30_MINUTES); + request.addOptionalAttendee(PERSON_B); + request.addOptionalAttendee(PERSON_A); + + Collection actual = query.query(events, request); + Collection expected = + Arrays.asList( + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + TimeRange.fromStartEnd(TIME_1230PM, TimeRange.END_OF_DAY, true)); + Assert.assertEquals(expected, actual); + } + + @Test + public void optimalNestedEvents() { + // Two optional attendees A, B. One mandatory attendee C. + // B's event lies within A. Only the end of day should be returned. + // + // Events : |-B--| |---C---| + // |-------A------| + // + // Day : |---------------------------| + // Options : |----| + + Collection events = + Arrays.asList( + new Event( + "Event 1", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + Arrays.asList(PERSON_A)), + new Event( + "Event 2", + TimeRange.fromStartEnd(TIME_0800AM, TIME_0830AM, false), + Arrays.asList(PERSON_B)), + new Event( + "Event 3", + TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), + Arrays.asList(PERSON_C))); + + MeetingRequest request = new MeetingRequest(Arrays.asList(PERSON_C), DURATION_30_MINUTES); + request.addOptionalAttendee(PERSON_B); + request.addOptionalAttendee(PERSON_A); + + Collection actual = query.query(events, request); + Collection expected = + Arrays.asList(TimeRange.fromStartEnd(TIME_1230PM, TimeRange.END_OF_DAY, true)); + Assert.assertEquals(expected, actual); + } + + @Test + public void optimalDoubleBooked() { + // One optional attendees A. One mandatory attendee C. + // A's events should be counted as one. The first half of the day should be returned. + // + // Events : |--A--| |---C--------| + // |-------A------| + // + // Day : |---------------------------| + // Options : |--------------| + + Collection events = + Arrays.asList( + new Event( + "Event 1", + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + Arrays.asList(PERSON_A)), + new Event( + "Event 2", + TimeRange.fromStartEnd(TIME_0800AM, TIME_0830AM, false), + Arrays.asList(PERSON_A)), + new Event( + "Event 3", + TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), + Arrays.asList(PERSON_C))); + + MeetingRequest request = new MeetingRequest(Arrays.asList(PERSON_C), DURATION_30_MINUTES); + request.addOptionalAttendee(PERSON_A); + + Collection actual = query.query(events, request); + Collection expected = + Arrays.asList(TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_1230PM, false)); + Assert.assertEquals(expected, actual); + } +} From 951e93bbbd4a85ce20cc4c9630865a920bbb55ff Mon Sep 17 00:00:00 2001 From: Claire Y Date: Thu, 18 Jun 2020 19:17:11 +0000 Subject: [PATCH 10/17] solution 3 passes all tests --- .../java/com/google/sps/FindMeetingQuery.java | 131 +++++++++--------- .../com/google/sps/FindMeetingQueryTest.java | 12 +- 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 1f25ffd..f572cc1 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -48,13 +48,14 @@ public int compare(Event a, Event b) { * long it needs to be */ public Collection query(Collection events, MeetingRequest request) { - ArrayList mandatoryAttendeesMeetingTimes = - getMandatoryAttendeesMeetingTimes(events, request); - if (mandatoryAttendeesMeetingTimes.isEmpty()) return mandatoryAttendeesMeetingTimes; - return getOptimalMeetingTimes( - mandatoryAttendeesMeetingTimes, - getChanges(events, new HashSet(request.getOptionalAttendees())), + getMandatoryAttendeesMeetingTimes( + events, request.getDuration(), new HashSet(request.getAttendees())), + getChanges( + getOptionalAttendeesFreeTimes( + events, + new HashSet(request.getOptionalAttendees()), + request.getDuration())), request.getDuration()); } @@ -63,34 +64,33 @@ public Collection query(Collection events, MeetingRequest requ * also fall in a good window and must be at least minTime long. Utilizes two pointers: one to * goodWindows, one to changeLog. * - * @param goodWindows all times in which a meeting can be scheduled - * @param optional all optional attendees to be considered + * @param goodWindows all meeting times satisfying mandatory attendees + * @param changeLog a change log of the number of available optional attendees over time * @param minTime the minimum time a meeting must last for */ private Collection getOptimalMeetingTimes( - ArrayList goodWindows, TreeMap changeLog, long minTime) { - System.out.println("changelog size=" + changeLog.size()); - // If there are no optional attendees free times, return all good windows. - if (changeLog.isEmpty()) { - return goodWindows; - } + ArrayList goodWindows, + ArrayList> changeLog, + long minTime) { ArrayList optimalMeetingTimes = new ArrayList(); - // TreeMap changeLog = getChanges(optionalAttendeesFreeTimes); - int currAttendance = 0, bestAttendance = 0, prevTime = 0; - Set> entrySet = - (Set>) changeLog.entrySet(); - List> c = new ArrayList>(entrySet); - for (int j = 0, i = 0; i < c.size(); ++i) { - int t = (Integer) c.get(i).getKey(); + // i is a pointer in changeLog, j is a poitner in goodWindows. + for (int j = 0, i = 0, currAttendance = 0, bestAttendance = 0, prevTime = 0; + i < changeLog.size(); + ++i) { + // Need to back up j in case we missed a good window. j = Math.max(0, j - 1); - while (j < goodWindows.size() && goodWindows.get(j).start() < t) { + + // Compares time range from changeLog[i-1] to changeLog[i]. If the current good window + // overlaps overlaps with this time range, we can proceed. + while (j < goodWindows.size() + && goodWindows.get(j).start() < (Integer) changeLog.get(i).getKey()) { bestAttendance = updateOptimalTimes( TimeRange.fromStartEnd( Math.max(goodWindows.get(j).start(), prevTime), - Math.min(goodWindows.get(j).end(), (Integer) c.get(i).getKey()), + Math.min(goodWindows.get(j).end(), (Integer) changeLog.get(i).getKey()), false), bestAttendance, currAttendance, @@ -98,8 +98,8 @@ private Collection getOptimalMeetingTimes( minTime); j++; } - prevTime = (Integer) c.get(i).getKey(); - currAttendance += (Integer) c.get(i).getValue(); + prevTime = (Integer) changeLog.get(i).getKey(); + currAttendance += (Integer) changeLog.get(i).getValue(); } // If there are no meeting times with at least one optional attendee, just return the good @@ -108,40 +108,42 @@ private Collection getOptimalMeetingTimes( } /** - * Returns a change log of how many optional attendees are available in at a certain time. Uses + * Returns a mapping of optional attendees to their free times. + * + * @param events all events to be considered + * @param optionalAttendees everyone who needs to attend this meeting + * @param minTime minimum length of time for meeting + */ + private HashMap> getOptionalAttendeesFreeTimes( + Collection events, HashSet optionalAttendees, long minTime) { + HashMap> times = new HashMap>(); + for (String attendee : optionalAttendees) { + HashSet attendeeSet = new HashSet(); + attendeeSet.add(attendee); + + // Find all possible meeting times for just this one attendee. Must do this to deal with + // double bookings. + times.put(attendee, getMandatoryAttendeesMeetingTimes(events, minTime, attendeeSet)); + } + return times; + } + + /** + * Returns a change log of the number of available optional attendees over time. First part of a * sweep-line algorithm. * * @param optionalAttendeesFreeTimes mapping of all attendees to their free times */ - private TreeMap getChanges( - Collection events, HashSet optionalAttendees) { + private ArrayList> getChanges( + HashMap> optionalAttendeesFreeTimes) { TreeMap changes = new TreeMap(); - if (optionalAttendees.isEmpty()) return changes; - changes.put( - TimeRange.START_OF_DAY, - optionalAttendees.size()); // assume everyone is free at the start of the day - changes.put(TimeRange.END_OF_DAY + 1, -optionalAttendees.size()); - for (Event event : events) { - int count = 0; - for (String attendee : optionalAttendees) { - if (event.getAttendees().contains(attendee)) { - count++; - } - } - if (count == 0) { - continue; + for (Map.Entry e : optionalAttendeesFreeTimes.entrySet()) { + for (TimeRange time : (ArrayList) e.getValue()) { + changes.put(time.start(), changes.getOrDefault(time.start(), 0) + 1); + changes.put(time.end(), changes.getOrDefault(time.end(), 0) - 1); } - System.out.println(event.getWhen().toString() + "," + count); - int s = event.getWhen().start(), e = event.getWhen().end(); - changes.put(s, changes.getOrDefault(s, 0) - count); - changes.put(e, changes.getOrDefault(e, 0) + count); } - - for (Map.Entry e : changes.entrySet()) { - System.out.println((Integer) e.getKey() + ": " + (Integer) e.getValue()); - } - - return changes; + return new ArrayList>(changes.entrySet()); } /** @@ -159,7 +161,6 @@ private int updateOptimalTimes( int currAttendance, ArrayList optimalMeetingTimes, long minTime) { - System.out.println(bestAttendance + " vs. " + currAttendance + "," + time.duration()); if (time.duration() >= minTime) { // Clear out all former optimal meeting times. They aren't the most optimal anymore. @@ -175,25 +176,23 @@ private int updateOptimalTimes( } /** - * Returns all meeting times that satisfy all mandatory attendees of request. + * Returns all meeting times that satisfy all mandatory attendees of request. Sorted in ascending + * order. * * @param events all events to be considered - * @param request information about the meeting, including attendees, optional attendees, and how - * long it needs to be + * @param minTime minimum length of time for meeting + * @param mandatoryAttendees everyone who needs to attend this meeting */ private ArrayList getMandatoryAttendeesMeetingTimes( - Collection events, MeetingRequest request) { + Collection events, long minTime, HashSet mandatoryAttendees) { ArrayList relevantEvents = - getRelevantEvents( - new HashSet(request.getAttendees()), new ArrayList(events)); + getRelevantEvents(mandatoryAttendees, new ArrayList(events)); ArrayList possibleMeetingTimes = new ArrayList(); // Need to check this so we don't access out of bounds when we add first gap. if (relevantEvents.isEmpty()) { addIfLongEnough( - TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), - possibleMeetingTimes, - request.getDuration()); + TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); return possibleMeetingTimes; } Collections.sort(relevantEvents, ORDER_BY_START_ASC); @@ -202,7 +201,7 @@ private ArrayList getMandatoryAttendeesMeetingTimes( addIfLongEnough( TimeRange.fromStartEnd(0, relevantEvents.get(0).getWhen().start(), false), possibleMeetingTimes, - request.getDuration()); + minTime); int end = relevantEvents.get(0).getWhen().end(); for (Event event : relevantEvents) { // event can be merged with current time range @@ -214,15 +213,13 @@ private ArrayList getMandatoryAttendeesMeetingTimes( addIfLongEnough( TimeRange.fromStartEnd(end, event.getWhen().start(), false), possibleMeetingTimes, - request.getDuration()); + minTime); end = event.getWhen().end(); } // Add the last one we were tracking. addIfLongEnough( - TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), - possibleMeetingTimes, - request.getDuration()); + TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); return possibleMeetingTimes; } diff --git a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java index 635b409..17bf14a 100644 --- a/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java +++ b/walkthroughs/week-5-tdd/project/src/test/java/com/google/sps/FindMeetingQueryTest.java @@ -666,8 +666,8 @@ public void optimalDoubleBooked() { // One optional attendees A. One mandatory attendee C. // A's events should be counted as one. The first half of the day should be returned. // - // Events : |--A--| |---C--------| - // |-------A------| + // Events : |--A--||---C--------| + // |----A------| // // Day : |---------------------------| // Options : |--------------| @@ -676,15 +676,15 @@ public void optimalDoubleBooked() { Arrays.asList( new Event( "Event 1", - TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0900AM, false), + TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0830AM, false), Arrays.asList(PERSON_A)), new Event( "Event 2", - TimeRange.fromStartEnd(TIME_0800AM, TIME_0830AM, false), + TimeRange.fromStartEnd(TIME_0800AM, TIME_0930AM, false), Arrays.asList(PERSON_A)), new Event( "Event 3", - TimeRange.fromStartEnd(TIME_0900AM, TIME_1230PM, false), + TimeRange.fromStartEnd(TIME_0930AM, TimeRange.END_OF_DAY, true), Arrays.asList(PERSON_C))); MeetingRequest request = new MeetingRequest(Arrays.asList(PERSON_C), DURATION_30_MINUTES); @@ -692,7 +692,7 @@ public void optimalDoubleBooked() { Collection actual = query.query(events, request); Collection expected = - Arrays.asList(TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_1230PM, false)); + Arrays.asList(TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0930AM, false)); Assert.assertEquals(expected, actual); } } From 3b4e919458ddcab5428bd664fbd9c406d213bb27 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Thu, 18 Jun 2020 19:18:06 +0000 Subject: [PATCH 11/17] more readable --- .../src/main/java/com/google/sps/FindMeetingQuery.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index f572cc1..a3f3562 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -82,8 +82,8 @@ private Collection getOptimalMeetingTimes( // Need to back up j in case we missed a good window. j = Math.max(0, j - 1); - // Compares time range from changeLog[i-1] to changeLog[i]. If the current good window - // overlaps overlaps with this time range, we can proceed. + // Compares time range from changeLog[i-1] to changeLog[i] to all good windows + // that overlap with this time range. while (j < goodWindows.size() && goodWindows.get(j).start() < (Integer) changeLog.get(i).getKey()) { bestAttendance = From b9eccf9cdf7bcac69593ac62233a868e90bbb2a8 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Fri, 19 Jun 2020 13:44:41 +0000 Subject: [PATCH 12/17] tweaks --- .../java/com/google/sps/FindMeetingQuery.java | 107 ++++++++++-------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index a3f3562..448d514 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -48,63 +48,72 @@ public int compare(Event a, Event b) { * long it needs to be */ public Collection query(Collection events, MeetingRequest request) { - return getOptimalMeetingTimes( + ArrayList mandatoryAttendeesMeetingTimes = getMandatoryAttendeesMeetingTimes( - events, request.getDuration(), new HashSet(request.getAttendees())), - getChanges( - getOptionalAttendeesFreeTimes( - events, - new HashSet(request.getOptionalAttendees()), - request.getDuration())), - request.getDuration()); + events, request.getDuration(), new HashSet(request.getAttendees())); + Collection optimalMeetingTimes = + getOptimalMeetingTimes( + mandatoryAttendeesMeetingTimes, + getChangesInOptionalAttendeesAttendance( + getOptionalAttendeesFreeTimes( + events, + new HashSet(request.getOptionalAttendees()), + request.getDuration())), + request.getDuration()); + + // If there are no meeting times with at least one optional attendee, just return + // mandatoryAttendeesMeetingTimes. + return optimalMeetingTimes.isEmpty() ? mandatoryAttendeesMeetingTimes : optimalMeetingTimes; } /** * Returns all meeting times that allow the most optional attendees to attend. These times must - * also fall in a good window and must be at least minTime long. Utilizes two pointers: one to - * goodWindows, one to changeLog. + * also fall in a time satisfying all mandatory attendees and must be at least minTime long. + * Utilizes two pointers: one to mandatoryAttendeesMeetingTimes, one to changeLog. * - * @param goodWindows all meeting times satisfying mandatory attendees + * @param mandatoryAttendeesMeetingTimes all meeting times satisfying mandatory attendees * @param changeLog a change log of the number of available optional attendees over time * @param minTime the minimum time a meeting must last for */ private Collection getOptimalMeetingTimes( - ArrayList goodWindows, - ArrayList> changeLog, + ArrayList mandatoryAttendeesMeetingTimes, + TreeMap changes, long minTime) { ArrayList optimalMeetingTimes = new ArrayList(); + ArrayList> changeLog = + new ArrayList>(changes.entrySet()); - // i is a pointer in changeLog, j is a poitner in goodWindows. - for (int j = 0, i = 0, currAttendance = 0, bestAttendance = 0, prevTime = 0; - i < changeLog.size(); - ++i) { - // Need to back up j in case we missed a good window. - j = Math.max(0, j - 1); + // changeLogIndexis a pointer in changeLog, mandatoryAttendeesMeetingTimesIndex is a pointer in mandatoryAttendeesMeetingTimes. + int mandatoryAttendeesMeetingTimesIndex = 0, currAttendance = 0, bestAttendance = 0, prevTime = 0; + for (int changeLogIndex = 0; changeLogIndex < changeLog.size(); ++changeLogIndex) { + // Need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range in mandatoryAttendeesMeetingTimes. + mandatoryAttendeesMeetingTimesIndex = Math.max(0, mandatoryAttendeesMeetingTimesIndex - 1); - // Compares time range from changeLog[i-1] to changeLog[i] to all good windows - // that overlap with this time range. - while (j < goodWindows.size() - && goodWindows.get(j).start() < (Integer) changeLog.get(i).getKey()) { + // Compares time range from previous time in changeLog to current time in changeLog + // mandatoryAttendeesMeetingTimes that overlap with this time range. + for (; + mandatoryAttendeesMeetingTimesIndex < mandatoryAttendeesMeetingTimes.size() + && mandatoryAttendeesMeetingTimes.get( mandatoryAttendeesMeetingTimesIndex).start() + < (Integer) changeLog.get(changeLogIndex).getKey(); + mandatoryAttendeesMeetingTimesIndex++) { bestAttendance = updateOptimalTimes( TimeRange.fromStartEnd( - Math.max(goodWindows.get(j).start(), prevTime), - Math.min(goodWindows.get(j).end(), (Integer) changeLog.get(i).getKey()), + Math.max(mandatoryAttendeesMeetingTimes.get( mandatoryAttendeesMeetingTimesIndex).start(), prevTime), + Math.min( + mandatoryAttendeesMeetingTimes.get( mandatoryAttendeesMeetingTimesIndex).end(), + (Integer) changeLog.get(changeLogIndex).getKey()), false), bestAttendance, currAttendance, - optimalMeetingTimes, - minTime); - j++; + minTime, + optimalMeetingTimes); } - prevTime = (Integer) changeLog.get(i).getKey(); - currAttendance += (Integer) changeLog.get(i).getValue(); + prevTime = (Integer) changeLog.get(changeLogIndex).getKey(); + currAttendance += (Integer) changeLog.get(changeLogIndex).getValue(); } - - // If there are no meeting times with at least one optional attendee, just return the good - // windows. - return optimalMeetingTimes.isEmpty() ? goodWindows : optimalMeetingTimes; + return optimalMeetingTimes; } /** @@ -134,7 +143,7 @@ private HashMap> getOptionalAttendeesFreeTimes( * * @param optionalAttendeesFreeTimes mapping of all attendees to their free times */ - private ArrayList> getChanges( + private TreeMap getChangesInOptionalAttendeesAttendance( HashMap> optionalAttendeesFreeTimes) { TreeMap changes = new TreeMap(); for (Map.Entry e : optionalAttendeesFreeTimes.entrySet()) { @@ -143,7 +152,7 @@ private ArrayList> getChanges( changes.put(time.end(), changes.getOrDefault(time.end(), 0) - 1); } } - return new ArrayList>(changes.entrySet()); + return changes; } /** @@ -152,25 +161,27 @@ private ArrayList> getChanges( * @param time current meeting time range * @param bestAttendance best attendance of optional attendees seen so far * @param currAttendance attendance optional attendees of current meeting - * @param optionalAttendeesFreeTimes all attendees and their free times * @param minTime minimum meeting time + * @param optimalMeetingTimes all meetings times with best optional attendees attendance we've + * encountered so far */ private int updateOptimalTimes( TimeRange time, int bestAttendance, int currAttendance, - ArrayList optimalMeetingTimes, - long minTime) { - if (time.duration() >= minTime) { + long minTime, + ArrayList optimalMeetingTimes) { + if (time.duration() < minTime) { + return bestAttendance; + } - // Clear out all former optimal meeting times. They aren't the most optimal anymore. - if (currAttendance > bestAttendance) { - bestAttendance = currAttendance; - optimalMeetingTimes.clear(); - } - if (currAttendance == bestAttendance) { - optimalMeetingTimes.add(time); - } + // Clear out all former optimal meeting times. They aren't the most optimal anymore. + if (currAttendance > bestAttendance) { + bestAttendance = currAttendance; + optimalMeetingTimes.clear(); + } + if (currAttendance == bestAttendance) { + optimalMeetingTimes.add(time); } return bestAttendance; } From 88b11debda3d781b88565a7aaa2dc869335688e5 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Fri, 19 Jun 2020 14:05:35 +0000 Subject: [PATCH 13/17] encapsulated logic into SingleMeetingResolver --- .../java/com/google/sps/FindMeetingQuery.java | 437 ++++++++++-------- 1 file changed, 232 insertions(+), 205 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 448d514..b795715 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -27,9 +27,11 @@ import java.util.Set; import java.util.TreeMap; -/** Lists optimal meeting times based on meeting information it takes in. */ +/** + * Supports a query function that lists optimal meeting times based on meeting information it takes + * in. + */ public final class FindMeetingQuery { - private static final Comparator ORDER_BY_START_ASC = new Comparator() { @Override @@ -38,238 +40,263 @@ public int compare(Event a, Event b) { } }; - /** - * Returns a list of time periods in which the meeting, specified by request, could happen. If no - * time exists for all optional and mandatory attendees, find the time slot(s) that allow - * mandatory attendees and the greatest possible number of optional attendees to attend. - * - * @param events the events to be considered - * @param request information about the meeting, including attendees, optional attendees, and how - * long it needs to be - */ - public Collection query(Collection events, MeetingRequest request) { - ArrayList mandatoryAttendeesMeetingTimes = - getMandatoryAttendeesMeetingTimes( - events, request.getDuration(), new HashSet(request.getAttendees())); - Collection optimalMeetingTimes = - getOptimalMeetingTimes( - mandatoryAttendeesMeetingTimes, - getChangesInOptionalAttendeesAttendance( - getOptionalAttendeesFreeTimes( - events, - new HashSet(request.getOptionalAttendees()), - request.getDuration())), - request.getDuration()); + // All logic needed to find optimal meeting times with one instance of meeting information and + // events to be considered. + private static class SingleMeetingResolver { + // all events to be considered + private final Collection events; - // If there are no meeting times with at least one optional attendee, just return - // mandatoryAttendeesMeetingTimes. - return optimalMeetingTimes.isEmpty() ? mandatoryAttendeesMeetingTimes : optimalMeetingTimes; - } + // information about the meeting, including attendees, optional attendees, and how long it needs + // to be + private final MeetingRequest request; - /** - * Returns all meeting times that allow the most optional attendees to attend. These times must - * also fall in a time satisfying all mandatory attendees and must be at least minTime long. - * Utilizes two pointers: one to mandatoryAttendeesMeetingTimes, one to changeLog. - * - * @param mandatoryAttendeesMeetingTimes all meeting times satisfying mandatory attendees - * @param changeLog a change log of the number of available optional attendees over time - * @param minTime the minimum time a meeting must last for - */ - private Collection getOptimalMeetingTimes( - ArrayList mandatoryAttendeesMeetingTimes, - TreeMap changes, - long minTime) { + // + private ArrayList optimalMeetingTimes = new ArrayList(); - ArrayList optimalMeetingTimes = new ArrayList(); - ArrayList> changeLog = - new ArrayList>(changes.entrySet()); + SingleMeetingResolver(Collection events, MeetingRequest request) { + this.events = events; + this.request = request; + } - // changeLogIndexis a pointer in changeLog, mandatoryAttendeesMeetingTimesIndex is a pointer in mandatoryAttendeesMeetingTimes. - int mandatoryAttendeesMeetingTimesIndex = 0, currAttendance = 0, bestAttendance = 0, prevTime = 0; - for (int changeLogIndex = 0; changeLogIndex < changeLog.size(); ++changeLogIndex) { - // Need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range in mandatoryAttendeesMeetingTimes. - mandatoryAttendeesMeetingTimesIndex = Math.max(0, mandatoryAttendeesMeetingTimesIndex - 1); + /** + * Returns a list of time periods in which the meeting, specified by request, could happen. If + * no time exists for all optional and mandatory attendees, find the time slot(s) that allow + * mandatory attendees and the greatest possible number of optional attendees to attend. + */ + Collection resolveBestTime() { + ArrayList mandatoryAttendeesMeetingTimes = + getMandatoryAttendeesMeetingTimes( + request.getDuration(), new HashSet(request.getAttendees())); + Collection optimalMeetingTimes = + getOptimalMeetingTimes( + mandatoryAttendeesMeetingTimes, + getChangesInOptionalAttendeesAttendance( + getOptionalAttendeesFreeTimes( + new HashSet(request.getOptionalAttendees()), request.getDuration())), + request.getDuration()); - // Compares time range from previous time in changeLog to current time in changeLog - // mandatoryAttendeesMeetingTimes that overlap with this time range. - for (; - mandatoryAttendeesMeetingTimesIndex < mandatoryAttendeesMeetingTimes.size() - && mandatoryAttendeesMeetingTimes.get( mandatoryAttendeesMeetingTimesIndex).start() - < (Integer) changeLog.get(changeLogIndex).getKey(); - mandatoryAttendeesMeetingTimesIndex++) { - bestAttendance = - updateOptimalTimes( - TimeRange.fromStartEnd( - Math.max(mandatoryAttendeesMeetingTimes.get( mandatoryAttendeesMeetingTimesIndex).start(), prevTime), - Math.min( - mandatoryAttendeesMeetingTimes.get( mandatoryAttendeesMeetingTimesIndex).end(), - (Integer) changeLog.get(changeLogIndex).getKey()), - false), - bestAttendance, - currAttendance, - minTime, - optimalMeetingTimes); - } - prevTime = (Integer) changeLog.get(changeLogIndex).getKey(); - currAttendance += (Integer) changeLog.get(changeLogIndex).getValue(); + // If there are no meeting times with at least one optional attendee, just return + // mandatoryAttendeesMeetingTimes. + return optimalMeetingTimes.isEmpty() ? mandatoryAttendeesMeetingTimes : optimalMeetingTimes; } - return optimalMeetingTimes; - } - /** - * Returns a mapping of optional attendees to their free times. - * - * @param events all events to be considered - * @param optionalAttendees everyone who needs to attend this meeting - * @param minTime minimum length of time for meeting - */ - private HashMap> getOptionalAttendeesFreeTimes( - Collection events, HashSet optionalAttendees, long minTime) { - HashMap> times = new HashMap>(); - for (String attendee : optionalAttendees) { - HashSet attendeeSet = new HashSet(); - attendeeSet.add(attendee); + /** + * Returns all meeting times that allow the most optional attendees to attend. These times must + * also fall in a time satisfying all mandatory attendees and must be at least minTime long. + * Utilizes two pointers: one to mandatoryAttendeesMeetingTimes, one to changeLog. + * + * @param mandatoryAttendeesMeetingTimes all meeting times satisfying mandatory attendees + * @param changeLog a change log of the number of available optional attendees over time + * @param minTime the minimum time a meeting must last for + */ + private Collection getOptimalMeetingTimes( + ArrayList mandatoryAttendeesMeetingTimes, + TreeMap changes, + long minTime) { - // Find all possible meeting times for just this one attendee. Must do this to deal with - // double bookings. - times.put(attendee, getMandatoryAttendeesMeetingTimes(events, minTime, attendeeSet)); - } - return times; - } + ArrayList optimalMeetingTimes = new ArrayList(); + ArrayList> changeLog = + new ArrayList>(changes.entrySet()); - /** - * Returns a change log of the number of available optional attendees over time. First part of a - * sweep-line algorithm. - * - * @param optionalAttendeesFreeTimes mapping of all attendees to their free times - */ - private TreeMap getChangesInOptionalAttendeesAttendance( - HashMap> optionalAttendeesFreeTimes) { - TreeMap changes = new TreeMap(); - for (Map.Entry e : optionalAttendeesFreeTimes.entrySet()) { - for (TimeRange time : (ArrayList) e.getValue()) { - changes.put(time.start(), changes.getOrDefault(time.start(), 0) + 1); - changes.put(time.end(), changes.getOrDefault(time.end(), 0) - 1); + // changeLogIndexis a pointer in changeLog, mandatoryAttendeesMeetingTimesIndex is a pointer + // in mandatoryAttendeesMeetingTimes. + int mandatoryAttendeesMeetingTimesIndex = 0, + currAttendance = 0, + bestAttendance = 0, + prevTime = 0; + for (int changeLogIndex = 0; changeLogIndex < changeLog.size(); ++changeLogIndex) { + // Need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range in + // mandatoryAttendeesMeetingTimes. + mandatoryAttendeesMeetingTimesIndex = Math.max(0, mandatoryAttendeesMeetingTimesIndex - 1); + + // Compares time range from previous time in changeLog to current time in changeLog + // mandatoryAttendeesMeetingTimes that overlap with this time range. + for (; + mandatoryAttendeesMeetingTimesIndex < mandatoryAttendeesMeetingTimes.size() + && mandatoryAttendeesMeetingTimes.get(mandatoryAttendeesMeetingTimesIndex).start() + < (Integer) changeLog.get(changeLogIndex).getKey(); + mandatoryAttendeesMeetingTimesIndex++) { + bestAttendance = + updateOptimalTimes( + TimeRange.fromStartEnd( + Math.max( + mandatoryAttendeesMeetingTimes + .get(mandatoryAttendeesMeetingTimesIndex) + .start(), + prevTime), + Math.min( + mandatoryAttendeesMeetingTimes + .get(mandatoryAttendeesMeetingTimesIndex) + .end(), + (Integer) changeLog.get(changeLogIndex).getKey()), + false), + bestAttendance, + currAttendance, + minTime, + optimalMeetingTimes); + } + prevTime = (Integer) changeLog.get(changeLogIndex).getKey(); + currAttendance += (Integer) changeLog.get(changeLogIndex).getValue(); } + return optimalMeetingTimes; } - return changes; - } - /** - * Updates optimalMeetingTimes based on current meeting time's attendance. - * - * @param time current meeting time range - * @param bestAttendance best attendance of optional attendees seen so far - * @param currAttendance attendance optional attendees of current meeting - * @param minTime minimum meeting time - * @param optimalMeetingTimes all meetings times with best optional attendees attendance we've - * encountered so far - */ - private int updateOptimalTimes( - TimeRange time, - int bestAttendance, - int currAttendance, - long minTime, - ArrayList optimalMeetingTimes) { - if (time.duration() < minTime) { - return bestAttendance; - } + /** + * Returns a mapping of optional attendees to their free times. + * + * @param events all events to be considered + * @param optionalAttendees everyone who needs to attend this meeting + * @param minTime minimum length of time for meeting + */ + private HashMap> getOptionalAttendeesFreeTimes( + HashSet optionalAttendees, long minTime) { + HashMap> times = new HashMap>(); + for (String attendee : optionalAttendees) { + HashSet attendeeSet = new HashSet(); + attendeeSet.add(attendee); - // Clear out all former optimal meeting times. They aren't the most optimal anymore. - if (currAttendance > bestAttendance) { - bestAttendance = currAttendance; - optimalMeetingTimes.clear(); + // Find all possible meeting times for just this one attendee. Must do this to deal with + // double bookings. + times.put(attendee, getMandatoryAttendeesMeetingTimes(minTime, attendeeSet)); + } + return times; } - if (currAttendance == bestAttendance) { - optimalMeetingTimes.add(time); + + /** + * Returns a change log of the number of available optional attendees over time. First part of a + * sweep-line algorithm. + * + * @param optionalAttendeesFreeTimes mapping of all attendees to their free times + */ + private TreeMap getChangesInOptionalAttendeesAttendance( + HashMap> optionalAttendeesFreeTimes) { + TreeMap changes = new TreeMap(); + for (Map.Entry e : optionalAttendeesFreeTimes.entrySet()) { + for (TimeRange time : (ArrayList) e.getValue()) { + changes.put(time.start(), changes.getOrDefault(time.start(), 0) + 1); + changes.put(time.end(), changes.getOrDefault(time.end(), 0) - 1); + } + } + return changes; } - return bestAttendance; - } - /** - * Returns all meeting times that satisfy all mandatory attendees of request. Sorted in ascending - * order. - * - * @param events all events to be considered - * @param minTime minimum length of time for meeting - * @param mandatoryAttendees everyone who needs to attend this meeting - */ - private ArrayList getMandatoryAttendeesMeetingTimes( - Collection events, long minTime, HashSet mandatoryAttendees) { - ArrayList relevantEvents = - getRelevantEvents(mandatoryAttendees, new ArrayList(events)); - ArrayList possibleMeetingTimes = new ArrayList(); + /** + * Updates optimalMeetingTimes based on current meeting time's attendance. + * + * @param time current meeting time range + * @param bestAttendance best attendance of optional attendees seen so far + * @param currAttendance attendance optional attendees of current meeting + * @param minTime minimum meeting time + * @param optimalMeetingTimes all meetings times with best optional attendees attendance we've + * encountered so far + */ + private int updateOptimalTimes( + TimeRange time, + int bestAttendance, + int currAttendance, + long minTime, + ArrayList optimalMeetingTimes) { + if (time.duration() < minTime) { + return bestAttendance; + } - // Need to check this so we don't access out of bounds when we add first gap. - if (relevantEvents.isEmpty()) { - addIfLongEnough( - TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); - return possibleMeetingTimes; + // Clear out all former optimal meeting times. They aren't the most optimal anymore. + if (currAttendance > bestAttendance) { + bestAttendance = currAttendance; + optimalMeetingTimes.clear(); + } + if (currAttendance == bestAttendance) { + optimalMeetingTimes.add(time); + } + return bestAttendance; } - Collections.sort(relevantEvents, ORDER_BY_START_ASC); - // Add first gap. - addIfLongEnough( - TimeRange.fromStartEnd(0, relevantEvents.get(0).getWhen().start(), false), - possibleMeetingTimes, - minTime); - int end = relevantEvents.get(0).getWhen().end(); - for (Event event : relevantEvents) { - // event can be merged with current time range - if (event.getWhen().start() <= end) { - end = Math.max(end, event.getWhen().end()); - continue; + /** + * Returns all meeting times that satisfy all mandatory attendees of request. Sorted in + * ascending order. + * + * @param minTime minimum length of time for meeting + * @param mandatoryAttendees everyone who needs to attend this meeting + */ + private ArrayList getMandatoryAttendeesMeetingTimes( + long minTime, HashSet mandatoryAttendees) { + ArrayList relevantEvents = getRelevantEvents(mandatoryAttendees); + ArrayList possibleMeetingTimes = new ArrayList(); + + // Need to check this so we don't access out of bounds when we add first gap. + if (relevantEvents.isEmpty()) { + addIfLongEnough( + TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); + return possibleMeetingTimes; } - // Add the time range we were tracking, start a new one from event. + Collections.sort(relevantEvents, ORDER_BY_START_ASC); + + // Add first gap. addIfLongEnough( - TimeRange.fromStartEnd(end, event.getWhen().start(), false), + TimeRange.fromStartEnd(0, relevantEvents.get(0).getWhen().start(), false), possibleMeetingTimes, minTime); - end = event.getWhen().end(); - } + int end = relevantEvents.get(0).getWhen().end(); + for (Event event : relevantEvents) { + // event can be merged with current time range + if (event.getWhen().start() <= end) { + end = Math.max(end, event.getWhen().end()); + continue; + } + // Add the time range we were tracking, start a new one from event. + addIfLongEnough( + TimeRange.fromStartEnd(end, event.getWhen().start(), false), + possibleMeetingTimes, + minTime); + end = event.getWhen().end(); + } - // Add the last one we were tracking. - addIfLongEnough( - TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); - return possibleMeetingTimes; - } + // Add the last one we were tracking. + addIfLongEnough( + TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); + return possibleMeetingTimes; + } - /** - * Adds range to ranges if it is long enough to fit in a meeting. - * - * @param range the range being considered - * @param ranges the list of ranges >= meetingDuration - * @param meetingDuration the duration of meeting to be scheduled - */ - private static void addIfLongEnough( - TimeRange range, List ranges, long meetingDuration) { - if (range.duration() >= meetingDuration) { - ranges.add(range); + /** + * Adds range to ranges if it is long enough to fit in a meeting. + * + * @param range the range being considered + * @param ranges the list of ranges >= meetingDuration + * @param meetingDuration the duration of meeting to be scheduled + */ + private static void addIfLongEnough( + TimeRange range, List ranges, long meetingDuration) { + if (range.duration() >= meetingDuration) { + ranges.add(range); + } } - } - /** - * Returns only those events that are attended by at least one attendee that is attending the - * meeting we are trying to schedule. More intuitively, an event is "relevant" if it is attended - * by at least one "relevant" attendee. - * - * @param requestAttendees the set of attendees attending the meeting ("relevant" people) - */ - private static ArrayList getRelevantEvents( - HashSet relevantAttendees, ArrayList events) { - ArrayList relevantEvents = new ArrayList(); - for (Event event : events) { - boolean isRelevant = false; - for (String person : event.getAttendees()) { - if (relevantAttendees.contains(person)) { - isRelevant = true; - break; + /** + * Returns only those events that are attended by at least one attendee that is attending the + * meeting we are trying to schedule. More intuitively, an event is "relevant" if it is attended + * by at least one "relevant" attendee. + * + * @param requestAttendees the set of attendees attending the meeting ("relevant" people) + */ + private ArrayList getRelevantEvents(HashSet relevantAttendees) { + ArrayList relevantEvents = new ArrayList(); + for (Event event : events) { + boolean isRelevant = false; + for (String person : event.getAttendees()) { + if (relevantAttendees.contains(person)) { + isRelevant = true; + break; + } + } + if (isRelevant) { + relevantEvents.add(event); } } - if (isRelevant) { - relevantEvents.add(event); - } + return relevantEvents; } - return relevantEvents; + } + + public Collection query(Collection events, MeetingRequest request) { + return new SingleMeetingResolver(events, request).resolveBestTime(); } } From 7a8941b5fa01e62c4c6bf30f82afd7a3ddd1c4a1 Mon Sep 17 00:00:00 2001 From: Claire Y Date: Fri, 19 Jun 2020 19:30:20 +0000 Subject: [PATCH 14/17] tweaks 2 --- .../java/com/google/sps/FindMeetingQuery.java | 165 +++++++----------- 1 file changed, 64 insertions(+), 101 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index b795715..4d039b4 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.stream.Collectors; import java.util.Comparator; import java.util.HashSet; import java.util.HashMap; @@ -50,12 +51,22 @@ private static class SingleMeetingResolver { // to be private final MeetingRequest request; - // private ArrayList optimalMeetingTimes = new ArrayList(); + // best attendance of optional attendees seen so far + private int bestAttendance; + + // attendance optional attendees of current meeting + private int currAttendance; + + // minimum meeting time + private long minTime; + SingleMeetingResolver(Collection events, MeetingRequest request) { this.events = events; this.request = request; + this.bestAttendance = this.currAttendance = 0; + this.minTime = request.getDuration(); } /** @@ -65,15 +76,11 @@ private static class SingleMeetingResolver { */ Collection resolveBestTime() { ArrayList mandatoryAttendeesMeetingTimes = - getMandatoryAttendeesMeetingTimes( - request.getDuration(), new HashSet(request.getAttendees())); - Collection optimalMeetingTimes = - getOptimalMeetingTimes( - mandatoryAttendeesMeetingTimes, - getChangesInOptionalAttendeesAttendance( - getOptionalAttendeesFreeTimes( - new HashSet(request.getOptionalAttendees()), request.getDuration())), - request.getDuration()); + getMandatoryAttendeesMeetingTimes(new HashSet(request.getAttendees())); + getOptimalMeetingTimes( + mandatoryAttendeesMeetingTimes, + getChangesInOptionalAttendeesAttendance( + getOptionalAttendeesFreeTimes(new HashSet(request.getOptionalAttendees())))); // If there are no meeting times with at least one optional attendee, just return // mandatoryAttendeesMeetingTimes. @@ -86,59 +93,38 @@ Collection resolveBestTime() { * Utilizes two pointers: one to mandatoryAttendeesMeetingTimes, one to changeLog. * * @param mandatoryAttendeesMeetingTimes all meeting times satisfying mandatory attendees - * @param changeLog a change log of the number of available optional attendees over time - * @param minTime the minimum time a meeting must last for + * @param changes a change log of the number of available optional attendees over time */ - private Collection getOptimalMeetingTimes( - ArrayList mandatoryAttendeesMeetingTimes, - TreeMap changes, - long minTime) { - - ArrayList optimalMeetingTimes = new ArrayList(); - ArrayList> changeLog = - new ArrayList>(changes.entrySet()); - - // changeLogIndexis a pointer in changeLog, mandatoryAttendeesMeetingTimesIndex is a pointer - // in mandatoryAttendeesMeetingTimes. - int mandatoryAttendeesMeetingTimesIndex = 0, - currAttendance = 0, - bestAttendance = 0, - prevTime = 0; - for (int changeLogIndex = 0; changeLogIndex < changeLog.size(); ++changeLogIndex) { - // Need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range in - // mandatoryAttendeesMeetingTimes. - mandatoryAttendeesMeetingTimesIndex = Math.max(0, mandatoryAttendeesMeetingTimesIndex - 1); - - // Compares time range from previous time in changeLog to current time in changeLog - // mandatoryAttendeesMeetingTimes that overlap with this time range. - for (; + private void getOptimalMeetingTimes( + ArrayList mandatoryAttendeesMeetingTimes, TreeMap changes) { + int mandatoryAttendeesMeetingTimesIndex = 0, prevTime = 0; + for (Map.Entry changeEntry : changes.entrySet()) { + // First need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range + // in + // mandatoryAttendeesMeetingTimes. Then Compares time range from previous time in changeLog + // to current time in changeLog with mandatoryAttendeesMeetingTimes that overlap with this + // time range. + for (mandatoryAttendeesMeetingTimesIndex = + Math.max(0, mandatoryAttendeesMeetingTimesIndex - 1); mandatoryAttendeesMeetingTimesIndex < mandatoryAttendeesMeetingTimes.size() && mandatoryAttendeesMeetingTimes.get(mandatoryAttendeesMeetingTimesIndex).start() - < (Integer) changeLog.get(changeLogIndex).getKey(); + < (Integer) changeEntry.getKey(); mandatoryAttendeesMeetingTimesIndex++) { - bestAttendance = - updateOptimalTimes( - TimeRange.fromStartEnd( - Math.max( - mandatoryAttendeesMeetingTimes - .get(mandatoryAttendeesMeetingTimesIndex) - .start(), - prevTime), - Math.min( - mandatoryAttendeesMeetingTimes - .get(mandatoryAttendeesMeetingTimesIndex) - .end(), - (Integer) changeLog.get(changeLogIndex).getKey()), - false), - bestAttendance, - currAttendance, - minTime, - optimalMeetingTimes); + updateOptimalTimes( + TimeRange.fromStartEnd( + Math.max( + mandatoryAttendeesMeetingTimes + .get(mandatoryAttendeesMeetingTimesIndex) + .start(), + prevTime), + Math.min( + mandatoryAttendeesMeetingTimes.get(mandatoryAttendeesMeetingTimesIndex).end(), + (Integer) changeEntry.getKey()), + false)); } - prevTime = (Integer) changeLog.get(changeLogIndex).getKey(); - currAttendance += (Integer) changeLog.get(changeLogIndex).getValue(); + prevTime = (Integer) changeEntry.getKey(); + currAttendance += (Integer) changeEntry.getValue(); } - return optimalMeetingTimes; } /** @@ -146,10 +132,9 @@ private Collection getOptimalMeetingTimes( * * @param events all events to be considered * @param optionalAttendees everyone who needs to attend this meeting - * @param minTime minimum length of time for meeting */ private HashMap> getOptionalAttendeesFreeTimes( - HashSet optionalAttendees, long minTime) { + HashSet optionalAttendees) { HashMap> times = new HashMap>(); for (String attendee : optionalAttendees) { HashSet attendeeSet = new HashSet(); @@ -157,7 +142,7 @@ private HashMap> getOptionalAttendeesFreeTimes( // Find all possible meeting times for just this one attendee. Must do this to deal with // double bookings. - times.put(attendee, getMandatoryAttendeesMeetingTimes(minTime, attendeeSet)); + times.put(attendee, getMandatoryAttendeesMeetingTimes(attendeeSet)); } return times; } @@ -184,20 +169,10 @@ private TreeMap getChangesInOptionalAttendeesAttendance( * Updates optimalMeetingTimes based on current meeting time's attendance. * * @param time current meeting time range - * @param bestAttendance best attendance of optional attendees seen so far - * @param currAttendance attendance optional attendees of current meeting - * @param minTime minimum meeting time - * @param optimalMeetingTimes all meetings times with best optional attendees attendance we've - * encountered so far */ - private int updateOptimalTimes( - TimeRange time, - int bestAttendance, - int currAttendance, - long minTime, - ArrayList optimalMeetingTimes) { + private void updateOptimalTimes(TimeRange time) { if (time.duration() < minTime) { - return bestAttendance; + return; } // Clear out all former optimal meeting times. They aren't the most optimal anymore. @@ -208,25 +183,23 @@ private int updateOptimalTimes( if (currAttendance == bestAttendance) { optimalMeetingTimes.add(time); } - return bestAttendance; } /** * Returns all meeting times that satisfy all mandatory attendees of request. Sorted in * ascending order. * - * @param minTime minimum length of time for meeting * @param mandatoryAttendees everyone who needs to attend this meeting */ private ArrayList getMandatoryAttendeesMeetingTimes( - long minTime, HashSet mandatoryAttendees) { + HashSet mandatoryAttendees) { ArrayList relevantEvents = getRelevantEvents(mandatoryAttendees); ArrayList possibleMeetingTimes = new ArrayList(); // Need to check this so we don't access out of bounds when we add first gap. if (relevantEvents.isEmpty()) { addIfLongEnough( - TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); + TimeRange.fromStartEnd(0, TimeRange.END_OF_DAY, true), possibleMeetingTimes); return possibleMeetingTimes; } Collections.sort(relevantEvents, ORDER_BY_START_ASC); @@ -234,8 +207,7 @@ private ArrayList getMandatoryAttendeesMeetingTimes( // Add first gap. addIfLongEnough( TimeRange.fromStartEnd(0, relevantEvents.get(0).getWhen().start(), false), - possibleMeetingTimes, - minTime); + possibleMeetingTimes); int end = relevantEvents.get(0).getWhen().end(); for (Event event : relevantEvents) { // event can be merged with current time range @@ -245,15 +217,13 @@ private ArrayList getMandatoryAttendeesMeetingTimes( } // Add the time range we were tracking, start a new one from event. addIfLongEnough( - TimeRange.fromStartEnd(end, event.getWhen().start(), false), - possibleMeetingTimes, - minTime); + TimeRange.fromStartEnd(end, event.getWhen().start(), false), possibleMeetingTimes); end = event.getWhen().end(); } // Add the last one we were tracking. addIfLongEnough( - TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), possibleMeetingTimes, minTime); + TimeRange.fromStartEnd(end, TimeRange.END_OF_DAY, true), possibleMeetingTimes); return possibleMeetingTimes; } @@ -262,11 +232,9 @@ private ArrayList getMandatoryAttendeesMeetingTimes( * * @param range the range being considered * @param ranges the list of ranges >= meetingDuration - * @param meetingDuration the duration of meeting to be scheduled */ - private static void addIfLongEnough( - TimeRange range, List ranges, long meetingDuration) { - if (range.duration() >= meetingDuration) { + private void addIfLongEnough(TimeRange range, List ranges) { + if (range.duration() >= minTime) { ranges.add(range); } } @@ -276,23 +244,18 @@ private static void addIfLongEnough( * meeting we are trying to schedule. More intuitively, an event is "relevant" if it is attended * by at least one "relevant" attendee. * - * @param requestAttendees the set of attendees attending the meeting ("relevant" people) + * @param relevantAttendees the set of attendees attending the meeting ("relevant" people) */ private ArrayList getRelevantEvents(HashSet relevantAttendees) { - ArrayList relevantEvents = new ArrayList(); - for (Event event : events) { - boolean isRelevant = false; - for (String person : event.getAttendees()) { - if (relevantAttendees.contains(person)) { - isRelevant = true; - break; - } - } - if (isRelevant) { - relevantEvents.add(event); - } - } - return relevantEvents; + return new ArrayList( + events.stream() + .filter( + e -> + e.getAttendees().stream() + .filter(relevantAttendees::contains) + .findAny() + .isPresent()) + .collect(Collectors.toList())); } } From cce101517de7be2c025e1beceacaf21bd0d49a2d Mon Sep 17 00:00:00 2001 From: Claire Y Date: Fri, 19 Jun 2020 20:27:50 +0000 Subject: [PATCH 15/17] docu tweak --- .../src/main/java/com/google/sps/FindMeetingQuery.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 4d039b4..820aae2 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -100,8 +100,7 @@ private void getOptimalMeetingTimes( int mandatoryAttendeesMeetingTimesIndex = 0, prevTime = 0; for (Map.Entry changeEntry : changes.entrySet()) { // First need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range - // in - // mandatoryAttendeesMeetingTimes. Then Compares time range from previous time in changeLog + // in mandatoryAttendeesMeetingTimes. Then compare time range from previous time in changeLog // to current time in changeLog with mandatoryAttendeesMeetingTimes that overlap with this // time range. for (mandatoryAttendeesMeetingTimesIndex = @@ -130,7 +129,6 @@ private void getOptimalMeetingTimes( /** * Returns a mapping of optional attendees to their free times. * - * @param events all events to be considered * @param optionalAttendees everyone who needs to attend this meeting */ private HashMap> getOptionalAttendeesFreeTimes( From 857fd09a73744206cbfe54b81f2fdb5e2360c5f5 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 26 Jun 2020 15:37:46 +0000 Subject: [PATCH 16/17] tweaks --- .../java/com/google/sps/FindMeetingQuery.java | 99 ++++++++----------- 1 file changed, 42 insertions(+), 57 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 820aae2..6ba7a98 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -44,29 +44,18 @@ public int compare(Event a, Event b) { // All logic needed to find optimal meeting times with one instance of meeting information and // events to be considered. private static class SingleMeetingResolver { - // all events to be considered + // events are all the events to be considered. private final Collection events; - // information about the meeting, including attendees, optional attendees, and how long it needs - // to be - private final MeetingRequest request; - + /* request contains information about the meeting, including attendees, optional attendees, and how long it needs to be. +*/ private final MeetingRequest request; private ArrayList optimalMeetingTimes = new ArrayList(); - - // best attendance of optional attendees seen so far - private int bestAttendance; - - // attendance optional attendees of current meeting - private int currAttendance; - - // minimum meeting time - private long minTime; + private long minMeetingDuration; SingleMeetingResolver(Collection events, MeetingRequest request) { this.events = events; this.request = request; - this.bestAttendance = this.currAttendance = 0; - this.minTime = request.getDuration(); + this.minMeetingDuration = request.getDuration(); } /** @@ -89,40 +78,56 @@ Collection resolveBestTime() { /** * Returns all meeting times that allow the most optional attendees to attend. These times must - * also fall in a time satisfying all mandatory attendees and must be at least minTime long. - * Utilizes two pointers: one to mandatoryAttendeesMeetingTimes, one to changeLog. + * also fall in a time satisfying all mandatory attendees and must be at least + * minMeetingDuration long. Utilizes two pointers: one to mandatoryAttendeesMeetingTimes, one to + * changeLog. * * @param mandatoryAttendeesMeetingTimes all meeting times satisfying mandatory attendees * @param changes a change log of the number of available optional attendees over time */ private void getOptimalMeetingTimes( ArrayList mandatoryAttendeesMeetingTimes, TreeMap changes) { - int mandatoryAttendeesMeetingTimesIndex = 0, prevTime = 0; + int j = 0, + prevTime = 0, + bestAttendance = 0, + currAttendance = 0; for (Map.Entry changeEntry : changes.entrySet()) { - // First need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range - // in mandatoryAttendeesMeetingTimes. Then compare time range from previous time in changeLog - // to current time in changeLog with mandatoryAttendeesMeetingTimes that overlap with this - // time range. - for (mandatoryAttendeesMeetingTimesIndex = - Math.max(0, mandatoryAttendeesMeetingTimesIndex - 1); - mandatoryAttendeesMeetingTimesIndex < mandatoryAttendeesMeetingTimes.size() - && mandatoryAttendeesMeetingTimes.get(mandatoryAttendeesMeetingTimesIndex).start() - < (Integer) changeEntry.getKey(); - mandatoryAttendeesMeetingTimesIndex++) { - updateOptimalTimes( + // First need to back up j in case we missed a time range + // in mandatoryAttendeesMeetingTimes. Then compare time range from previous time in + // changeLog to current time in changeLog with mandatoryAttendeesMeetingTimes that overlap + // with this time range. + for (j = + Math.max(0, j - 1); + j < mandatoryAttendeesMeetingTimes.size() + && mandatoryAttendeesMeetingTimes.get(j).start() + < (int) changeEntry.getKey(); + j++) { + TimeRange meetingRange = TimeRange.fromStartEnd( Math.max( mandatoryAttendeesMeetingTimes - .get(mandatoryAttendeesMeetingTimesIndex) + .get(j) .start(), prevTime), Math.min( - mandatoryAttendeesMeetingTimes.get(mandatoryAttendeesMeetingTimesIndex).end(), - (Integer) changeEntry.getKey()), - false)); + mandatoryAttendeesMeetingTimes.get(j).end(), + (int) changeEntry.getKey()), + false); + if (meetingRange.duration() < minMeetingDuration) { + continue; + } + + // Clear out all former optimal meeting times. They aren't the most optimal anymore. + if (currAttendance > bestAttendance) { + bestAttendance = currAttendance; + optimalMeetingTimes.clear(); + } + if (currAttendance == bestAttendance) { + optimalMeetingTimes.add(meetingRange); + } } - prevTime = (Integer) changeEntry.getKey(); - currAttendance += (Integer) changeEntry.getValue(); + prevTime = (int) changeEntry.getKey(); + currAttendance += (int) changeEntry.getValue(); } } @@ -163,26 +168,6 @@ private TreeMap getChangesInOptionalAttendeesAttendance( return changes; } - /** - * Updates optimalMeetingTimes based on current meeting time's attendance. - * - * @param time current meeting time range - */ - private void updateOptimalTimes(TimeRange time) { - if (time.duration() < minTime) { - return; - } - - // Clear out all former optimal meeting times. They aren't the most optimal anymore. - if (currAttendance > bestAttendance) { - bestAttendance = currAttendance; - optimalMeetingTimes.clear(); - } - if (currAttendance == bestAttendance) { - optimalMeetingTimes.add(time); - } - } - /** * Returns all meeting times that satisfy all mandatory attendees of request. Sorted in * ascending order. @@ -232,7 +217,7 @@ private ArrayList getMandatoryAttendeesMeetingTimes( * @param ranges the list of ranges >= meetingDuration */ private void addIfLongEnough(TimeRange range, List ranges) { - if (range.duration() >= minTime) { + if (range.duration() >= minMeetingDuration) { ranges.add(range); } } From bd55190df2c427930139072a942a68a279b76510 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 26 Jun 2020 18:27:12 +0000 Subject: [PATCH 17/17] cleaned up for loop --- .../java/com/google/sps/FindMeetingQuery.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java index 6ba7a98..e351e54 100644 --- a/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java +++ b/walkthroughs/week-5-tdd/project/src/main/java/com/google/sps/FindMeetingQuery.java @@ -48,7 +48,7 @@ private static class SingleMeetingResolver { private final Collection events; /* request contains information about the meeting, including attendees, optional attendees, and how long it needs to be. -*/ private final MeetingRequest request; + */ private final MeetingRequest request; private ArrayList optimalMeetingTimes = new ArrayList(); private long minMeetingDuration; @@ -87,32 +87,32 @@ Collection resolveBestTime() { */ private void getOptimalMeetingTimes( ArrayList mandatoryAttendeesMeetingTimes, TreeMap changes) { - int j = 0, + int mandatoryAttendeesMeetingTimesIndex = 0, prevTime = 0, bestAttendance = 0, currAttendance = 0; for (Map.Entry changeEntry : changes.entrySet()) { - // First need to back up j in case we missed a time range - // in mandatoryAttendeesMeetingTimes. Then compare time range from previous time in - // changeLog to current time in changeLog with mandatoryAttendeesMeetingTimes that overlap - // with this time range. - for (j = - Math.max(0, j - 1); - j < mandatoryAttendeesMeetingTimes.size() - && mandatoryAttendeesMeetingTimes.get(j).start() - < (int) changeEntry.getKey(); - j++) { + // First need to back up mandatoryAttendeesMeetingTimesIndex in case we missed a time range + // in mandatoryAttendeesMeetingTimes. + mandatoryAttendeesMeetingTimesIndex = Math.max(0, mandatoryAttendeesMeetingTimesIndex - 1); + + // Then compare time range from previous time in changeLog to current time in changeLog + // with mandatoryAttendeesMeetingTimes that overlap with this time range. + while (mandatoryAttendeesMeetingTimesIndex < mandatoryAttendeesMeetingTimes.size() + && mandatoryAttendeesMeetingTimes.get(mandatoryAttendeesMeetingTimesIndex).start() + < (int) changeEntry.getKey()) { TimeRange meetingRange = TimeRange.fromStartEnd( Math.max( mandatoryAttendeesMeetingTimes - .get(j) + .get(mandatoryAttendeesMeetingTimesIndex) .start(), prevTime), Math.min( - mandatoryAttendeesMeetingTimes.get(j).end(), + mandatoryAttendeesMeetingTimes.get(mandatoryAttendeesMeetingTimesIndex).end(), (int) changeEntry.getKey()), false); + mandatoryAttendeesMeetingTimesIndex++; if (meetingRange.duration() < minMeetingDuration) { continue; }