diff --git a/walkthroughs/week-5-tdd/project/pom.xml b/walkthroughs/week-5-tdd/project/pom.xml
index e4d5dce..8bb3582 100644
--- a/walkthroughs/week-5-tdd/project/pom.xml
+++ b/walkthroughs/week-5-tdd/project/pom.xml
@@ -30,6 +30,11 @@
2.8.6
+
+ com.google.collections
+ google-collections
+ 1.0
+
junit
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 16ce36e..8b4d212 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
@@ -14,6 +14,7 @@
package com.google.sps;
+import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -23,6 +24,9 @@
import java.util.List;
public final class FindMeetingQuery {
+ // Returns available meetings for day given events and request. query will return a list of TimeRanges including optional attendees
+ // if there is availability with optional attendees, and will otherwise return a list of TimeRanges for only required attendees. Returns
+ // an empty List object if there is no availability.
public Collection query(Collection events, MeetingRequest request) {
// In this case, long to int conversion is safe because duration can never exceed 2^32. Leaving duration as long
// leads to compile errors.
@@ -34,23 +38,73 @@ public Collection query(Collection events, MeetingRequest requ
}
// Seperate relevant TimeRanges from events, put into ArrayList and sort by ascending meeting start time
+ // Optional attendees are handled by running two sets of queries in parallel: a query if we are including optional attendees,
+ // and a query if we are only considering required attendees. If the optional attendee query is empty, then that means that
+ // there are no valid times if we include optional attendees, and optional attendees are ignored.
List attendedMeetings = new ArrayList<>();
+ List allAttendedMeetings = new ArrayList<>();
+
+ // Attendees is only required attendees, allAttendees includes optional attendees
+ Set requiredAttendees = new HashSet<>(request.getAttendees());
+ Set optionalAttendees = new HashSet<>(request.getOptionalAttendees());
+
for (Event event : events) {
// First check if the event in question contains people from request, add those meetings to attendedMeetings
- Set attendees = new HashSet<>(request.getAttendees());
- attendees.retainAll(event.getAttendees());
+ Set allAttendees = new HashSet<>(Sets.union(requiredAttendees, optionalAttendees));
+
+ requiredAttendees.retainAll(event.getAttendees());
+ allAttendees.retainAll(event.getAttendees());
- if (!attendees.isEmpty()){
+ if (!requiredAttendees.isEmpty()){
attendedMeetings.add(event.getWhen());
}
+
+ if (!allAttendees.isEmpty()) {
+ allAttendedMeetings.add(event.getWhen());
+ }
+
}
// Sort attendedMeetings so that we can filter out all nested meetings in next step
Collections.sort(attendedMeetings, TimeRange.ORDER_BY_START);
+ Collections.sort(allAttendedMeetings, TimeRange.ORDER_BY_START);
+
+ // All openings represents the availabilities WITH optional attendees, requiredOpenings represents only required attendees.
+ // if allOpenings is empty, that means that there is a conflict with optional attendees and optional attendees should be ignored.
+ List requiredOpenings = new ArrayList<>(findOpenings(attendedMeetings, duration));
+ List allOpenings = new ArrayList<>(findOpenings(allAttendedMeetings, duration));
+
+ // As-is, the user will not be aware whether this function is returning allOpenings or requiredOpenings. As-is, the function
+ // simply returns whichever is available.
+ return allOpenings.size() == 0 ? requiredOpenings : allOpenings;
+ }
+
+ // Takes in sorted list of meetings and returns next available TimeRange given, or null if none available with current startTime
+ private TimeRange findNextTime(List meetings, int startMeetingIndex, int duration) {
+ int startTime = meetings.get(startMeetingIndex).end();
- List validMeetings = new ArrayList<>();
+ // If this is the last meeting in the list and there's a meeting time that fits, return a meeting time.
+ if (startMeetingIndex == meetings.size() - 1) {
+ return (startTime + duration <= TimeRange.END_OF_DAY) ?
+ TimeRange.fromStartEnd(startTime, TimeRange.END_OF_DAY, true)
+ : null;
+ }
- for (TimeRange meeting : attendedMeetings) {
+ // If next meeting's start time is farther away than duration,
+ // return a TimeRange from startTime -> the start of the next meeting.
+ if (meetings.get(startMeetingIndex + 1).start() - startTime >= duration) {
+ return TimeRange.fromStartEnd(startTime, meetings.get(startMeetingIndex + 1).start(), false);
+ }
+ else {
+ return null;
+ }
+ }
+
+ // Takes in sorted list of TimeRanges, filters out nested meetings, and then passes into findNextTime
+ private List findOpenings(List meetings, int duration) {
+ List validMeetings = new ArrayList<>();
+
+ for (TimeRange meeting : meetings) {
int numMeetings = validMeetings.size();
@@ -85,25 +139,4 @@ public Collection query(Collection events, MeetingRequest requ
return openings;
}
-
- // Takes in sorted list of meetings and returns next available TimeRange given, or null if none available with current startTime
- private TimeRange findNextTime(List meetings, int startMeetingIndex, int duration) {
- int startTime = meetings.get(startMeetingIndex).end();
-
- // If this is the last meeting in the list and there's a meeting time that fits, return a meeting time.
- if (startMeetingIndex == meetings.size() - 1) {
- return (startTime + duration <= TimeRange.END_OF_DAY) ?
- TimeRange.fromStartEnd(startTime, TimeRange.END_OF_DAY, true)
- : null;
- }
-
- /* If next meeting's start time is farther away than duration,
- * return a TimeRange from startTime -> the start of the next meeting. */
- if (meetings.get(startMeetingIndex + 1).start() - startTime >= duration) {
- return TimeRange.fromStartEnd(startTime, meetings.get(startMeetingIndex + 1).start(), false);
- }
- else {
- return null;
- }
- }
}
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 bea1890..9668f5a 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
@@ -34,10 +34,12 @@ public final class FindMeetingQueryTest {
// Some people that we can use in our tests.
private static final String PERSON_A = "Person A";
private static final String PERSON_B = "Person B";
+ private static final String PERSON_C = "Person C";
// All dates are the first day of the year 2020.
private static final int TIME_0800AM = TimeRange.getTimeInMinutes(8, 0);
private static final int TIME_0830AM = TimeRange.getTimeInMinutes(8, 30);
+ private static final int TIME_0845AM = TimeRange.getTimeInMinutes(8, 45);
private static final int TIME_0900AM = TimeRange.getTimeInMinutes(9, 0);
private static final int TIME_0930AM = TimeRange.getTimeInMinutes(9, 30);
private static final int TIME_1000AM = TimeRange.getTimeInMinutes(10, 0);
@@ -270,4 +272,144 @@ public void notEnoughRoom() {
Assert.assertEquals(expected, actual);
}
+
+ @Test
+ public void optionalAttendeeNotAvailable() {
+ // Have each person have different events. We should see two options because each person has
+ // split the restricted times. Optional attendee C will not be considered because they are never
+ // available.
+ //
+ // Events : |--A--| |--B--|
+ // |-------------C---------------|
+ // Day : |-----------------------------|
+ // Options : |--1--| |--2--| |--3--|
+
+ Collection events = Arrays.asList(
+ new Event("Event 1", TimeRange.fromStartDuration(TIME_0800AM, DURATION_30_MINUTES),
+ Arrays.asList(PERSON_A)),
+ new Event("Event 2", TimeRange.fromStartDuration(TIME_0900AM, DURATION_30_MINUTES),
+ Arrays.asList(PERSON_B)),
+ new Event("Event 3", TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TimeRange.END_OF_DAY, true),
+ Arrays.asList(PERSON_C)));
+
+ MeetingRequest request =
+ new MeetingRequest(Arrays.asList(PERSON_A, PERSON_B), DURATION_30_MINUTES);
+
+ request.addOptionalAttendee(PERSON_C);
+
+ Collection actual = query.query(events, request);
+ Collection expected =
+ Arrays.asList(TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0800AM, false),
+ TimeRange.fromStartEnd(TIME_0830AM, TIME_0900AM, false),
+ TimeRange.fromStartEnd(TIME_0930AM, TimeRange.END_OF_DAY, true));
+
+ Assert.assertEquals(expected, actual);
+ }
+
+ @Test
+ public void optionalAttendeeIncluded() {
+ // Have each person have different events. We should see two options because each person has
+ // split the restricted times. Optional attendee Person C is available for some timeslots, but not all.
+ //
+ // Events : |--A--| |--B--|
+ // |--C--|
+ // Day : |-----------------------------|
+ // Options : |--1--| |--2--|
+
+ Collection events = Arrays.asList(
+ new Event("Event 1", TimeRange.fromStartDuration(TIME_0800AM, DURATION_30_MINUTES),
+ Arrays.asList(PERSON_A)),
+ new Event("Event 2", TimeRange.fromStartDuration(TIME_0900AM, DURATION_30_MINUTES),
+ Arrays.asList(PERSON_B)),
+ new Event("Event 3", TimeRange.fromStartDuration(TIME_0830AM, DURATION_30_MINUTES),
+ Arrays.asList(PERSON_C)));
+
+ MeetingRequest request =
+ new MeetingRequest(Arrays.asList(PERSON_A, PERSON_B), DURATION_30_MINUTES);
+ request.addOptionalAttendee(PERSON_C);
+
+ Collection actual = query.query(events, request);
+ Collection expected =
+ Arrays.asList(TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0800AM, false),
+ TimeRange.fromStartEnd(TIME_0930AM, TimeRange.END_OF_DAY, true));
+
+ Assert.assertEquals(expected, actual);
+ }
+
+ @Test
+ public void justEnoughRoomIgnoreOptional() {
+ // Have one person, but make it so that there is just enough room at one point in the day to
+ // have the meeting if you ignore optional attendee B.
+ //
+ // Events : |--A--| |----A----|
+ // |-B|
+ // Day : |---------------------|
+ // Options : |-----|
+
+ Collection events = Arrays.asList(
+ new Event("Event 1", TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0830AM, false),
+ Arrays.asList(PERSON_A)),
+ new Event("Event 2", TimeRange.fromStartEnd(TIME_0900AM, TimeRange.END_OF_DAY, true),
+ Arrays.asList(PERSON_A)),
+ new Event("Event 3", TimeRange.fromStartEnd(TIME_0830AM, TIME_0845AM, false),
+ Arrays.asList(PERSON_B)));
+
+ MeetingRequest request = new MeetingRequest(Arrays.asList(PERSON_A), DURATION_30_MINUTES);
+ request.addOptionalAttendee(PERSON_B);
+
+ Collection actual = query.query(events, request);
+ Collection expected =
+ Arrays.asList(TimeRange.fromStartDuration(TIME_0830AM, DURATION_30_MINUTES));
+
+ Assert.assertEquals(expected, actual);
+ }
+
+ public void onlyOptionalWithTime() {
+ // Two optional attendees with gaps in their schedules.
+ //
+ // Events : |--A--| |--B--|
+ // Day : |-----------------------------|
+ // Options : |--1--| |--2--| |--3--|
+
+ Collection events = Arrays.asList(
+ new Event("Event 1", TimeRange.fromStartEnd(TIME_0800AM, TIME_0900AM, false),
+ Arrays.asList(PERSON_A)),
+ new Event("Event 2", TimeRange.fromStartEnd(TIME_1000AM, TIME_1100AM, false),
+ Arrays.asList(PERSON_B)));
+
+ MeetingRequest request = new MeetingRequest(Arrays.asList(), DURATION_30_MINUTES);
+ request.addOptionalAttendee(PERSON_A);
+ request.addOptionalAttendee(PERSON_B);
+
+ Collection actual = query.query(events, request);
+ Collection expected =
+ Arrays.asList(TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_0800AM, false),
+ TimeRange.fromStartEnd(TIME_0900AM, TIME_1000AM, false),
+ TimeRange.fromStartEnd(TIME_1100AM, TimeRange.END_OF_DAY, true));
+
+ Assert.assertEquals(expected, actual);
+ }
+
+ public void onlyOptionalWithNoTime() {
+ // Two optional attendees with no gaps in their schedules. Should return empty.
+ //
+ // Events : |-----A-----||-------B--------|
+ // Day : |-----------------------------|
+ // Options :
+
+ Collection events = Arrays.asList(
+ new Event("Event 1", TimeRange.fromStartEnd(TimeRange.START_OF_DAY, TIME_1000AM, false),
+ Arrays.asList(PERSON_A)),
+ new Event("Event 2", TimeRange.fromStartEnd(TIME_1000AM, TimeRange.END_OF_DAY, true),
+ Arrays.asList(PERSON_B)));
+
+ MeetingRequest request = new MeetingRequest(Arrays.asList(), DURATION_30_MINUTES);
+ request.addOptionalAttendee(PERSON_A);
+ request.addOptionalAttendee(PERSON_B);
+
+ Collection actual = query.query(events, request);
+ Collection expected = Arrays.asList();
+
+ Assert.assertEquals(expected, actual);
+ }
}