Meeting planner
A day of meetings with their people: booking refuses a time when one of them is already in a meeting (half-open times, so back-to-back is fine), each person's busy time merged, the free time a group shares, and the fewest rooms the day needs with one way to assign them.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A day of meetings, each with its people. Booking refuses a time when one of them is already in a meeting. There is each person's busy time merged, the free time a group shares between 09:00 and 17:00, and the fewest rooms the day needs, with one way to assign them. It starts with seven meetings for Ana, Ben, Chloe and Dev.
main.eml- the menu, reading titles, names and times, and the reportsintervals.eml- overlap, shared minutes, sorting and merging intervals, free time, and the room count with an assignment
How each part works:
- Every time is minutes from midnight, and every meeting is half-open: 09:15-10:15 holds 09:15 but not 10:15. As in the corpus case
interval-overlap-detector, [a0, a1) and [b0, b1) overlap exactly when a0 < b1 and b0 < a1. So a meeting that starts as another ends is no clash. With <= instead, every back-to-back booking would be refused. - Booking checks each person against each of their meetings, and a refusal says how many minutes would clash.
- Busy time: a person's meetings sorted by start, each joining the last piece if it starts before or just as that piece ends.
- Shared free time: the meetings of everyone named, merged, and the gaps of at least the asked length between 09:00 and 17:00.
- Rooms: meetings taken by start time, each into the room that emptied first if it is free by then, otherwise into a new room. This never uses more rooms than the most meetings running at one moment - and that many are needed - so it is the fewest. That moment is always some meeting's start, so the program checks only those.
What is checked: menu choices 1 to 7; a title of 1 to 30 characters, not already used; names of letters, up to 12 each, separated by commas, each once; times as H:MM or HH:MM from 0:00, with 24:00 allowed as an end, and the end after the start; a length of 5 to 480 minutes. An empty answer cancels.
Sessions: sessions/basic.in gives: - the seven meetings, and Ana's busy time: 3 h in three pieces; - the free time Ana and Ben share (30 minutes or more), and Chloe and Dev (60 or more); - "planning" booked for Ana and Ben at 10:15, just as their design review ends; - "all-hands" at 09:30-10:20, which makes it 3 rooms, the rooms shown, then removed again: 2 rooms.
sessions/bad-input.in gives: - menu choices 0 and x; - a title already used and one too long; the names "Ana,, Ben", "Ana1" and "Ana, Ana"; the times 25:00 and 9:3, an end before the start and 24:01, then a cancel; - the name "Ana B", and Zoe, who has no meetings; - lengths 4 and abc; 480 minutes for Ana and 240 for Ben, Chloe and Dev, which no one has; - removing a meeting that does not exist; - a booking for Ben refused by a 1-minute clash, and one for Gus ending at 24:00.
Built on the verified corpus case interval-overlap-detector (half-open intervals and the a0 < b1 and b0 < a1 test, back-to-back included).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Meeting planner ==
A day of meetings: book them, see who is busy, find shared free time, count the rooms.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 0
Pick a number from 1 to 7.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> x
Pick a number from 1 to 7.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 2
title> standup
There is a meeting called standup already.
title> a title that is far too long for it
A title is up to 30 characters.
title> review
people (like Ana, Ben)> Ana,, Ben
Type names of letters, up to 12 each, separated by commas, each once.
people (like Ana, Ben)> Ana1
Type names of letters, up to 12 each, separated by commas, each once.
people (like Ana, Ben)> Ana, Ana
Type names of letters, up to 12 each, separated by commas, each once.
people (like Ana, Ben)> Ana, Ben
from (like 9:30)> 25:00
Type a time like 9:30, from 0:00 to 23:59.
from (like 9:30)> 9:3
Type a time like 9:30, from 0:00 to 23:59.
from (like 9:30)> 12:00
to> 11:00
A meeting has to end after it starts.
to> 24:01
Type a time like 10:15, up to 24:00.
to>
Cancelled.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 3
person> Ana B
Type a name of letters, up to 12.
person> Zoe
Nobody called Zoe has a meeting.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 4
people (like Ana, Ben)> Ana
at least how many minutes (5 to 480)> 4
Type a whole number from 5 to 480.
at least how many minutes (5 to 480)> abc
Type a whole number from 5 to 480.
at least how many minutes (5 to 480)> 480
Between 09:00 and 17:00, Ana is never free for 480 minutes.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 4
people (like Ana, Ben)> Ben, Chloe, Dev
at least how many minutes (5 to 480)> 240
Between 09:00 and 17:00, Ben, Chloe and Dev are never all free for 240 minutes.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 6
title> nothing
No meeting is called nothing.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 2
title> late
people (like Ana, Ben)> Ben
from (like 9:30)> 14:59
to> 15:30
Ben is already in deploy window, 13:30-15:00 - 1 minute of it would clash.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 2
title> evening
people (like Ana, Ben)> Gus
from (like 9:30)> 23:00
to> 24:00
Booked evening, 23:00-24:00.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 3
person> Gus
Gus is busy 23:00-24:00 - 1 h in all.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 7
Bye.
What was typed (42 lines)
0
x
2
standup
a title that is far too long for it
review
Ana,, Ben
Ana1
Ana, Ana
Ana, Ben
25:00
9:3
12:00
11:00
24:01
3
Ana B
Zoe
4
Ana
4
abc
480
4
Ben, Chloe, Dev
240
6
nothing
2
late
Ben
14:59
15:30
2
evening
Gus
23:00
24:00
3
Gus
7
basic
interpreter: byte-equal== Meeting planner ==
A day of meetings: book them, see who is busy, find shared free time, count the rooms.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 1
09:00-09:15 standup (Ana, Ben and Chloe)
09:15-10:15 design review (Ana and Ben)
10:00-10:30 one-to-one (Chloe and Dev)
12:00-13:00 lunch talk (Ben, Chloe and Dev)
13:00-14:00 retro (Ana and Dev)
13:30-15:00 deploy window (Ben)
15:30-16:15 interview (Ana and Chloe)
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 3
person> Ana
Ana is busy 09:00-10:15, 13:00-14:00, 15:30-16:15 - 3 h in all.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 4
people (like Ana, Ben)> Ana, Ben
at least how many minutes (5 to 480)> 30
Between 09:00 and 17:00, Ana and Ben are all free for 30 minutes or more at 10:15-12:00 (1 h 45 min), 15:00-15:30 (30 min), 16:15-17:00 (45 min).
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 4
people (like Ana, Ben)> Chloe, Dev
at least how many minutes (5 to 480)> 60
Between 09:00 and 17:00, Chloe and Dev are all free for 60 minutes or more at 10:30-12:00 (1 h 30 min), 14:00-15:30 (1 h 30 min).
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 2
title> planning
people (like Ana, Ben)> Ana, Ben
from (like 9:30)> 10:15
to> 11:00
Booked planning, 10:15-11:00.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 2
title> all-hands
people (like Ana, Ben)> Eli
from (like 9:30)> 9:30
to> 10:20
Booked all-hands, 09:30-10:20.
The day now needs 3 rooms.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 5
At most 3 meetings run at once, first at 10:00 - so 3 rooms are enough:
room 1: 09:00-09:15 standup, 09:15-10:15 design review, 10:15-11:00 planning, 13:30-15:00 deploy window
room 2: 09:30-10:20 all-hands, 12:00-13:00 lunch talk, 15:30-16:15 interview
room 3: 10:00-10:30 one-to-one, 13:00-14:00 retro
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 6
title> all-hands
Removed all-hands.
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 5
At most 2 meetings run at once, first at 10:00 - so 2 rooms are enough:
room 1: 09:00-09:15 standup, 09:15-10:15 design review, 10:15-11:00 planning, 13:00-14:00 retro, 15:30-16:15 interview
room 2: 10:00-10:30 one-to-one, 12:00-13:00 lunch talk, 13:30-15:00 deploy window
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 1
09:00-09:15 standup (Ana, Ben and Chloe)
09:15-10:15 design review (Ana and Ben)
10:00-10:30 one-to-one (Chloe and Dev)
10:15-11:00 planning (Ana and Ben)
12:00-13:00 lunch talk (Ben, Chloe and Dev)
13:00-14:00 retro (Ana and Dev)
13:30-15:00 deploy window (Ben)
15:30-16:15 interview (Ana and Chloe)
1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit
choice> 7
Bye.
What was typed (25 lines)
1
3
Ana
4
Ana, Ben
30
4
Chloe, Dev
60
2
planning
Ana, Ben
10:15
11:00
2
all-hands
Eli
9:30
10:20
5
6
all-hands
5
1
7
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P070 meeting planner: a day of meetings, each with its people. Booking
# refuses a time when one of them is already in a meeting; there is each
# person's busy time merged, the free time a group shares, and the fewest
# rooms the day needs.
import intervals
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def digits(s):
# The value of a string of 1 or 2 digits, or -1.
if len(s) == 0 or len(s) > 2:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def number(s):
# A whole number of up to 4 digits, or -1.
if len(s) == 0 or len(s) > 4:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def minutes(k):
if k == 1:
return "1 minute"
return str(k) + " minutes"
def clock(m):
# Minutes from midnight as HH:MM.
0 => h
m => r
while r >= 60:
r - 60 => r
h + 1 => h
"" => hh
if h < 10:
"0" => hh
"" => mm
if r < 10:
"0" => mm
return hh + str(h) + ":" + mm + str(r)
def span(x):
return clock(x[0]) + "-" + clock(x[1])
def length(m):
# 135 -> "2 h 15 min"
0 => h
m => r
while r >= 60:
r - 60 => r
h + 1 => h
if h == 0:
return str(r) + " min"
if r == 0:
return str(h) + " h"
return str(h) + " h " + str(r) + " min"
def time_of(s):
# "9:30" or "09:30" as minutes from midnight; 24:00 is the end of the
# day. -1 if it is not a time.
0 => k
while k < len(s) and s[k] != ":":
k + 1 => k
if k == len(s) or len(s) - k != 3:
return -1
digits(s[0:k]) => h
digits(s[k + 1:len(s)]) => m
if h < 0 or m < 0 or m > 59 or h > 24 or (h == 24 and m > 0):
return -1
return h * 60 + m
def joined(names):
# ["Ana", "Ben", "Dev"] -> "Ana, Ben and Dev"
"" => s
for i in [0:len(names) - 1]:
if i > 0 and i == len(names) - 1:
s + " and " => s
elif i > 0:
s + ", " => s
s + names[i] => s
return s
def is_name(s):
if len(s) == 0 or len(s) > 12:
return False
for c in s:
if not ((c >= "a" and c <= "z") or (c >= "A" and c <= "Z")):
return False
return True
def names_of(s):
# "Ana, Ben" -> ["Ana", "Ben"]; [] if a name is not letters (up to 12)
# or comes twice.
[] => out
0 => i
while i <= len(s):
i => j
while j < len(s) and s[j] != ",":
j + 1 => j
trim(s[i:j]) => name
if not is_name(name):
return []
for other in out:
if other == name:
return []
out + [name] => out
j + 1 => i
return out
def attends(meeting, name):
for p in meeting[3]:
if p == name:
return True
return False
def spans(meetings):
[0] * len(meetings) => xs
for i in [0:len(meetings) - 1]:
[meetings[i][1], meetings[i][2]] => xs[i]
return xs
def in_order(meetings):
# Indices of the meetings by start time.
[0] * len(meetings) => xs
for i in [0:len(meetings) - 1]:
[meetings[i][1], meetings[i][2], i] => xs[i]
intervals.by_start(xs) => xs
[0] * len(xs) => out
for i in [0:len(xs) - 1]:
xs[i][2] => out[i]
return out
def show(meetings):
if len(meetings) == 0:
"No meetings booked." ^0
return
for i in in_order(meetings):
meetings[i] => m
(" " + span([m[1], m[2]]) + " " + m[0] + " (" + joined(m[3]) + ")") ^0
def ask_times():
# [start, end] or [] when cancelled.
while True:
trim(input("from (like 9:30)> ")) => a
if a == "":
return []
time_of(a) => start
if start >= 0 and start < 1440:
while True:
trim(input("to> ")) => b
if b == "":
return []
time_of(b) => end
if end > start:
return [start, end]
if end >= 0:
"A meeting has to end after it starts." ^0
else:
"Type a time like 10:15, up to 24:00." ^0
else:
"Type a time like 9:30, from 0:00 to 23:59." ^0
def add(meetings):
while True:
trim(input("title> ")) => title
if title == "":
"Cancelled." ^0
return meetings
True => fresh
for m in meetings:
if m[0] == title:
False => fresh
if len(title) > 30:
"A title is up to 30 characters." ^0
elif not fresh:
("There is a meeting called " + title + " already.") ^0
else:
while True:
trim(input("people (like Ana, Ben)> ")) => s
if s == "":
"Cancelled." ^0
return meetings
names_of(s) => people
if len(people) > 0:
ask_times() => t
if len(t) == 0:
"Cancelled." ^0
return meetings
for p in people:
for m in meetings:
if attends(m, p) and intervals.overlaps(t, [m[1], m[2]]):
(p + " is already in " + m[0] + ", " + span([m[1], m[2]]) + " - " + minutes(intervals.shared(t, [m[1], m[2]])) + " of it would clash.") ^0
return meetings
intervals.rooms(spans(meetings))[0] => before
meetings + [[title, t[0], t[1], people]] => meetings
intervals.rooms(spans(meetings))[0] => after
("Booked " + title + ", " + span(t) + ".") ^0
if after > before:
("The day now needs " + str(after) + " rooms.") ^0
return meetings
"Type names of letters, up to 12 each, separated by commas, each once." ^0
def remove(meetings):
trim(input("title> ")) => title
if title == "":
"Cancelled." ^0
return meetings
[] => out
for m in meetings:
if m[0] != title:
out + [m] => out
if len(out) == len(meetings):
("No meeting is called " + title + ".") ^0
else:
("Removed " + title + ".") ^0
return out
def busy(meetings):
while True:
trim(input("person> ")) => name
if name == "":
"Cancelled." ^0
return
if is_name(name):
[] => xs
for m in meetings:
if attends(m, name):
xs + [[m[1], m[2]]] => xs
if len(xs) == 0:
("Nobody called " + name + " has a meeting.") ^0
return
intervals.merged(xs) => ms
0 => total
"" => s
for x in ms:
if s != "":
s + ", " => s
s + span(x) => s
total + x[1] - x[0] => total
(name + " is busy " + s + " - " + length(total) + " in all.") ^0
return
"Type a name of letters, up to 12." ^0
def common_free(meetings):
while True:
trim(input("people (like Ana, Ben)> ")) => s
if s == "":
"Cancelled." ^0
return
names_of(s) => people
if len(people) > 0:
while True:
trim(input("at least how many minutes (5 to 480)> ")) => a
if a == "":
"Cancelled." ^0
return
number(a) => least
if least < 5 or least > 480:
"Type a whole number from 5 to 480." ^0
else:
[] => xs
for m in meetings:
for p in people:
if attends(m, p):
xs + [[m[1], m[2]]] => xs
intervals.free(xs, 540, 1020, least) => gaps
if len(gaps) == 0 and len(people) == 1:
("Between 09:00 and 17:00, " + people[0] + " is never free for " + minutes(least) + ".") ^0
return
if len(gaps) == 0:
("Between 09:00 and 17:00, " + joined(people) + " are never all free for " + minutes(least) + ".") ^0
return
"" => line
for g in gaps:
if line != "":
line + ", " => line
line + span(g) + " (" + length(g[1] - g[0]) + ")" => line
if len(people) == 1:
("Between 09:00 and 17:00, " + people[0] + " is free for " + minutes(least) + " or more at " + line + ".") ^0
else:
("Between 09:00 and 17:00, " + joined(people) + " are all free for " + minutes(least) + " or more at " + line + ".") ^0
return
else:
"Type names of letters, up to 12 each, separated by commas, each once." ^0
def rooms(meetings):
if len(meetings) == 0:
"No meetings, no rooms." ^0
return
spans(meetings) => xs
intervals.busiest(xs) => b
intervals.rooms(xs) => r
("At most " + str(b[0]) + " meetings run at once, first at " + clock(b[1]) + " - so " + str(r[0]) + " rooms are enough:") ^0
for k in [1:r[0]]:
"" => line
for i in in_order(meetings):
if r[1][i] == k:
if line != "":
line + ", " => line
line + span([meetings[i][1], meetings[i][2]]) + " " + meetings[i][0] => line
(" room " + str(k) + ": " + line) ^0
[
["standup", 540, 555, ["Ana", "Ben", "Chloe"]],
["design review", 555, 615, ["Ana", "Ben"]],
["one-to-one", 600, 630, ["Chloe", "Dev"]],
["lunch talk", 720, 780, ["Ben", "Chloe", "Dev"]],
["retro", 780, 840, ["Ana", "Dev"]],
["deploy window", 810, 900, ["Ben"]],
["interview", 930, 975, ["Ana", "Chloe"]],
] => meetings
"== Meeting planner ==" ^0
"A day of meetings: book them, see who is busy, find shared free time, count the rooms." ^0
True => running
while running:
"" ^0
"1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
show(meetings)
elif choice == "2":
add(meetings) => meetings
elif choice == "3":
busy(meetings)
elif choice == "4":
common_free(meetings)
elif choice == "5":
rooms(meetings)
elif choice == "6":
remove(meetings) => meetings
elif choice == "7":
False => running
else:
"Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import intervals
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def digits(s):
if len(s) == 0 or len(s) > 2:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def number(s):
if len(s) == 0 or len(s) > 4:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def minutes(k):
if k == 1:
return "1 minute"
return str(k) + " minutes"
def clock(m):
h = 0
r = m
while r >= 60:
r = r - 60
h = h + 1
hh = ""
if h < 10:
hh = "0"
mm = ""
if r < 10:
mm = "0"
return hh + str(h) + ":" + mm + str(r)
def span(x):
return clock(x[0]) + "-" + clock(x[1])
def length(m):
h = 0
r = m
while r >= 60:
r = r - 60
h = h + 1
if h == 0:
return str(r) + " min"
if r == 0:
return str(h) + " h"
return str(h) + " h " + str(r) + " min"
def time_of(s):
k = 0
while k < len(s) and s[k] != ":":
k = k + 1
if k == len(s) or len(s) - k != 3:
return -1
h = digits(s[0:k])
m = digits(s[k + 1:len(s)])
if h < 0 or m < 0 or m > 59 or h > 24 or h == 24 and m > 0:
return -1
return h * 60 + m
def joined(names):
s = ""
for i in range(0, len(names)):
if i > 0 and i == len(names) - 1:
s = s + " and "
elif i > 0:
s = s + ", "
s = s + names[i]
return s
def is_name(s):
if len(s) == 0 or len(s) > 12:
return False
for c in s:
if not (c >= "a" and c <= "z" or c >= "A" and c <= "Z"):
return False
return True
def names_of(s):
out = []
i = 0
while i <= len(s):
j = i
while j < len(s) and s[j] != ",":
j = j + 1
name = trim(s[i:j])
if not is_name(name):
return []
for other in out:
if other == name:
return []
out = out + [name]
i = j + 1
return out
def attends(meeting, name):
for p in meeting[3]:
if p == name:
return True
return False
def spans(meetings):
xs = [0] * len(meetings)
for i in range(0, len(meetings)):
xs[i] = [meetings[i][1], meetings[i][2]]
return xs
def in_order(meetings):
xs = [0] * len(meetings)
for i in range(0, len(meetings)):
xs[i] = [meetings[i][1], meetings[i][2], i]
xs = intervals.by_start(xs)
out = [0] * len(xs)
for i in range(0, len(xs)):
out[i] = xs[i][2]
return out
def show(meetings):
if len(meetings) == 0:
print("No meetings booked.")
return
for i in in_order(meetings):
m = meetings[i]
print(" " + span([m[1], m[2]]) + " " + m[0] + " (" + joined(m[3]) + ")")
def ask_times():
while True:
a = trim(input("from (like 9:30)> "))
if a == "":
return []
start = time_of(a)
if start >= 0 and start < 1440:
while True:
b = trim(input("to> "))
if b == "":
return []
end = time_of(b)
if end > start:
return [start, end]
if end >= 0:
print("A meeting has to end after it starts.")
else:
print("Type a time like 10:15, up to 24:00.")
else:
print("Type a time like 9:30, from 0:00 to 23:59.")
def add(meetings):
while True:
title = trim(input("title> "))
if title == "":
print("Cancelled.")
return meetings
fresh = True
for m in meetings:
if m[0] == title:
fresh = False
if len(title) > 30:
print("A title is up to 30 characters.")
elif not fresh:
print("There is a meeting called " + title + " already.")
else:
while True:
s = trim(input("people (like Ana, Ben)> "))
if s == "":
print("Cancelled.")
return meetings
people = names_of(s)
if len(people) > 0:
t = ask_times()
if len(t) == 0:
print("Cancelled.")
return meetings
for p in people:
for m in meetings:
if attends(m, p) and intervals.overlaps(t, [m[1], m[2]]):
print(p + " is already in " + m[0] + ", " + span([m[1], m[2]]) + " - " + minutes(intervals.shared(t, [m[1], m[2]])) + " of it would clash.")
return meetings
before = intervals.rooms(spans(meetings))[0]
meetings = meetings + [[title, t[0], t[1], people]]
after = intervals.rooms(spans(meetings))[0]
print("Booked " + title + ", " + span(t) + ".")
if after > before:
print("The day now needs " + str(after) + " rooms.")
return meetings
print("Type names of letters, up to 12 each, separated by commas, each once.")
def remove(meetings):
title = trim(input("title> "))
if title == "":
print("Cancelled.")
return meetings
out = []
for m in meetings:
if m[0] != title:
out = out + [m]
if len(out) == len(meetings):
print("No meeting is called " + title + ".")
else:
print("Removed " + title + ".")
return out
def busy(meetings):
while True:
name = trim(input("person> "))
if name == "":
print("Cancelled.")
return
if is_name(name):
xs = []
for m in meetings:
if attends(m, name):
xs = xs + [[m[1], m[2]]]
if len(xs) == 0:
print("Nobody called " + name + " has a meeting.")
return
ms = intervals.merged(xs)
total = 0
s = ""
for x in ms:
if s != "":
s = s + ", "
s = s + span(x)
total = total + x[1] - x[0]
print(name + " is busy " + s + " - " + length(total) + " in all.")
return
print("Type a name of letters, up to 12.")
def common_free(meetings):
while True:
s = trim(input("people (like Ana, Ben)> "))
if s == "":
print("Cancelled.")
return
people = names_of(s)
if len(people) > 0:
while True:
a = trim(input("at least how many minutes (5 to 480)> "))
if a == "":
print("Cancelled.")
return
least = number(a)
if least < 5 or least > 480:
print("Type a whole number from 5 to 480.")
else:
xs = []
for m in meetings:
for p in people:
if attends(m, p):
xs = xs + [[m[1], m[2]]]
gaps = intervals.free(xs, 540, 1020, least)
if len(gaps) == 0 and len(people) == 1:
print("Between 09:00 and 17:00, " + people[0] + " is never free for " + minutes(least) + ".")
return
if len(gaps) == 0:
print("Between 09:00 and 17:00, " + joined(people) + " are never all free for " + minutes(least) + ".")
return
line = ""
for g in gaps:
if line != "":
line = line + ", "
line = line + span(g) + " (" + length(g[1] - g[0]) + ")"
if len(people) == 1:
print("Between 09:00 and 17:00, " + people[0] + " is free for " + minutes(least) + " or more at " + line + ".")
else:
print("Between 09:00 and 17:00, " + joined(people) + " are all free for " + minutes(least) + " or more at " + line + ".")
return
else:
print("Type names of letters, up to 12 each, separated by commas, each once.")
def rooms(meetings):
if len(meetings) == 0:
print("No meetings, no rooms.")
return
xs = spans(meetings)
b = intervals.busiest(xs)
r = intervals.rooms(xs)
print("At most " + str(b[0]) + " meetings run at once, first at " + clock(b[1]) + " - so " + str(r[0]) + " rooms are enough:")
for k in range(1, r[0]+1):
line = ""
for i in in_order(meetings):
if r[1][i] == k:
if line != "":
line = line + ", "
line = line + span([meetings[i][1], meetings[i][2]]) + " " + meetings[i][0]
print(" room " + str(k) + ": " + line)
meetings = [["standup", 540, 555, ["Ana", "Ben", "Chloe"]], ["design review", 555, 615, ["Ana", "Ben"]], ["one-to-one", 600, 630, ["Chloe", "Dev"]], ["lunch talk", 720, 780, ["Ben", "Chloe", "Dev"]], ["retro", 780, 840, ["Ana", "Dev"]], ["deploy window", 810, 900, ["Ben"]], ["interview", 930, 975, ["Ana", "Chloe"]]]
print("== Meeting planner ==")
print("A day of meetings: book them, see who is busy, find shared free time, count the rooms.")
running = True
while running:
print("")
print("1) meetings 2) book 3) someone's busy time 4) shared free time 5) rooms needed 6) remove 7) quit")
choice = trim(input("choice> "))
if choice == "1":
show(meetings)
elif choice == "2":
meetings = add(meetings)
elif choice == "3":
busy(meetings)
elif choice == "4":
common_free(meetings)
elif choice == "5":
rooms(meetings)
elif choice == "6":
meetings = remove(meetings)
elif choice == "7":
running = False
else:
print("Pick a number from 1 to 7.")
print("Bye.")
intervals.eml
eml# P070 meeting planner - times as minutes from midnight, every interval
# half-open: [start, end) holds start but not end, so a meeting that ends
# at 10:00 and one that starts at 10:00 do not overlap.
def overlaps(a, b):
# The corpus case interval-overlap-detector: [a0, a1) and [b0, b1)
# overlap exactly when a0 < b1 and b0 < a1. With <= instead, meetings
# back to back would clash; checking one side only would call
# 09:00-10:00 and 11:00-12:00 a clash.
return a[0] < b[1] and b[0] < a[1]
def shared(a, b):
# How many minutes two intervals have in common (0 if none).
a[0] => lo
if b[0] > lo:
b[0] => lo
a[1] => hi
if b[1] < hi:
b[1] => hi
if hi > lo:
return hi - lo
return 0
def by_start(xs):
# A copy sorted by start, then end - insertion sort, which keeps equal
# intervals in the order they came.
[0] * len(xs) => out
for i in [0:len(xs) - 1]:
xs[i] => out[i]
for i in [1:len(out) - 1]:
out[i] => x
i - 1 => j
while j >= 0 and (out[j][0] > x[0] or (out[j][0] == x[0] and out[j][1] > x[1])):
out[j] => out[j + 1]
j - 1 => j
x => out[j + 1]
return out
def merged(xs):
# The busy time the intervals cover, as the fewest intervals: sorted by
# start, each one either joins the last (it starts before or just as
# the last ends) or begins a new one.
by_start(xs) => s
[] => out
for x in s:
if len(out) > 0 and x[0] <= out[len(out) - 1][1]:
out[len(out) - 1] => last
if x[1] > last[1]:
[last[0], x[1]] => out[len(out) - 1]
else:
out + [[x[0], x[1]]] => out
return out
def free(busy, lo, hi, least):
# The gaps of at least `least` minutes between lo and hi that no
# interval in busy covers.
merged(busy) => m
[] => out
lo => t
for x in m:
if x[1] > lo and x[0] < hi:
if x[0] - t >= least:
out + [[t, x[0]]] => out
if x[1] > t:
x[1] => t
if hi - t >= least:
out + [[t, hi]] => out
return out
def rooms(xs):
# The fewest rooms that hold every meeting, and one way to do it.
# Meetings are taken by start time; each goes into the room that
# emptied first, if that room is free by then, or into a new room.
# That never uses more rooms than the most meetings running at one
# moment, and that many are needed - so it is the fewest.
# Returns [room count, [room number for each meeting in xs]].
[0] * len(xs) => order
for i in [0:len(xs) - 1]:
[xs[i][0], xs[i][1], i] => order[i]
by_start(order) => order
[] => ends
[0] * len(xs) => room
for x in order:
-1 => best
for r in [0:len(ends) - 1]:
if ends[r] <= x[0] and (best == -1 or ends[r] < ends[best]):
r => best
if best == -1:
ends + [x[1]] => ends
len(ends) - 1 => best
else:
x[1] => ends[best]
best + 1 => room[x[2]]
return [len(ends), room]
def busiest(xs):
# The most meetings running at one moment, and the first moment it
# happens: it is always some meeting's start time.
0 => most
0 => when
for x in xs:
0 => k
for y in xs:
if y[0] <= x[0] and x[0] < y[1]:
k + 1 => k
if k > most or (k == most and x[0] < when):
k => most
x[0] => when
return [most, when]
Python projection (intervals.py)
def overlaps(a, b):
return a[0] < b[1] and b[0] < a[1]
def shared(a, b):
lo = a[0]
if b[0] > lo:
lo = b[0]
hi = a[1]
if b[1] < hi:
hi = b[1]
if hi > lo:
return hi - lo
return 0
def by_start(xs):
out = [0] * len(xs)
for i in range(0, len(xs)):
out[i] = xs[i]
for i in range(1, len(out)):
x = out[i]
j = i - 1
while j >= 0 and (out[j][0] > x[0] or out[j][0] == x[0] and out[j][1] > x[1]):
out[j + 1] = out[j]
j = j - 1
out[j + 1] = x
return out
def merged(xs):
s = by_start(xs)
out = []
for x in s:
if len(out) > 0 and x[0] <= out[len(out) - 1][1]:
last = out[len(out) - 1]
if x[1] > last[1]:
out[len(out) - 1] = [last[0], x[1]]
else:
out = out + [[x[0], x[1]]]
return out
def free(busy, lo, hi, least):
m = merged(busy)
out = []
t = lo
for x in m:
if x[1] > lo and x[0] < hi:
if x[0] - t >= least:
out = out + [[t, x[0]]]
if x[1] > t:
t = x[1]
if hi - t >= least:
out = out + [[t, hi]]
return out
def rooms(xs):
order = [0] * len(xs)
for i in range(0, len(xs)):
order[i] = [xs[i][0], xs[i][1], i]
order = by_start(order)
ends = []
room = [0] * len(xs)
for x in order:
best = -1
for r in range(0, len(ends)):
if ends[r] <= x[0] and (best == -1 or ends[r] < ends[best]):
best = r
if best == -1:
ends = ends + [x[1]]
best = len(ends) - 1
else:
ends[best] = x[1]
room[x[2]] = best + 1
return [len(ends), room]
def busiest(xs):
most = 0
when = 0
for x in xs:
k = 0
for y in xs:
if y[0] <= x[0] and x[0] < y[1]:
k = k + 1
if k > most or k == most and x[0] < when:
most = k
when = x[0]
return [most, when]