# Calculating how many Pomodori fit into a period of time

I've written some code to calculate how many Pomodori can fit into a period of time, including long and short breaks. I'm looking for alternative solutions because what I have at the moment doesn't feel right. I think I'm missing something.

A Pomodoro is an interval of time 25 minutes long and after each one there should be a short 5 minute break. But every 4x Pomodori should be followed by a long 15 minutes break. So we have the following:

╔═══════╦════════╦═════════════╗
║ Time  ║ Length ║ Activity    ║
╠═══════╬════════╬═════════════╣
║ 12:00 ║ 25:00  ║ Pomodoro    ║
║ 12:25 ║ 05:00  ║ Short Break ║
║ 12:30 ║ 25:00  ║ Pomodoro    ║
║ 12:55 ║ 05:00  ║ Short Break ║
║ 13:00 ║ 25:00  ║ Pomodoro    ║
║ 13:25 ║ 05:00  ║ Short Break ║
║ 13:30 ║ 25:00  ║ Pomodoro    ║
║ 13:55 ║ 15:00  ║ Long Break  ║
║ 14:10 ║ 25:00  ║ Pomodoro    ║
╚═══════╩════════╩═════════════╝


At the moment I use a loop to populate a list and then delete the last element if it's not a Pomodoro, as there's no point in ending the list of Pomodori with a break.

pomodori = []
iterations = 0
seconds = 3 * 60 * 60

while seconds > 0:
# zero and even numbers are always Pomodori
if iterations % 2 == 0 or iterations == 0:
pomodori.append('pomodoro')
seconds -= 25 * 60
else:
quotient, remainder = divmod(iterations+1, 4)

# if the quotient is even and the remainder is zero, then we
# are just after a 4x Pomodori and should add a long break
if quotient % 2 == 0 and remainder == 0:
pomodori.append('long-break')
seconds -= 15 * 60
# otherwise, we're at a short break
else:
pomodori.append('short-break')
seconds -= 5 * 60

iterations += 1

# remove breaks that are not followed by a Pomodoro
if pomodori[-1] != 'pomodoro':
del pomodori[-1]


EDIT:

I've gone ahead with jonrsharpe's solution. The code is here for those interested.

-
Just FYI, in your table your short breaks are currently 5 seconds long (00:05)... "I move away from the microphone to breathe in" –  Phil H Jun 11 at 15:16
Haha, well spotted. :-) –  Matt Deacalion Stevens Jun 11 at 15:26
@PhilH - thanks for the unexpected Chocolate Rain reference. :) –  mskfisher Jun 11 at 15:27
Is the question about "how many" interval we can fit or about generating the sequence ? The answers are good so far but could be even more concise if the question was more precise. –  Josay Jun 11 at 16:03
@Josay I could have phrased it better but initially I wanted the quantities of each type of unit (Pomodoro, short break, long break). My first implementation ended up creating a sequence because I'm terrible at maths and couldn't work out a mathematical way of doing it. Now I'm happy with the sequence solution, it's even given me some new ideas for the UI. –  Matt Deacalion Stevens Jun 11 at 16:28

I would split this into two functions; one that just creates the sequence, and one that develops the programme for a time period:

def pomodori():
"""Generate pomodori and breaks indefinitely."""
n = 0
while True:
if n % 8 == 7:
yield ('long break', 15)
elif n % 2 == 0:
yield ('pomodoro', 25)
else:
yield ('short break', 5)
n += 1

def programme(len_=120):
"""Create the pattern for a session of specified length."""
t = 0
prog = []
for pn, tn in pomodori():
if t + tn > len_:
break
t += tn
prog.append(pn)
if prog and prog[-1].endswith('break'):
prog.pop()
return prog


Example:

>>> programme(180)
'pomodoro', 'short break', 'pomodoro', 'short break', 'pomodoro', 'short break', 'pomodoro', 'long break', 'pomodoro']


I have chosen to write in minutes, as all of your lengths are in minutes. You can put handlers around this to convert to or from seconds or hours, but this gives a good start.

-
What is n in your pomodori function? –  BeetDemGuise Jun 11 at 11:53
@DarinDouglass oops... Fixed! –  jonrsharpe Jun 11 at 12:58
Consider using itertools.cycle() for your pomodori(). –  200_success Jun 11 at 13:00
your long break gives a '25' break and not a '15' break like the OP –  Malachi Jun 11 at 13:10
@Malachi good spot, fixed. –  jonrsharpe Jun 11 at 13:29

I think it would be simpler just to apply some ingenuity and partially unroll the loop. While it's possible to fit in a long cycle (pomodoro, short break, pomodoro, short break, pomodoro, short break, pomodoro, long break) with time left over, then do that. While it's possible to fit in a short cycle (pomodoro, short break) with time left over, do that.

I'd also define some constants for readability.

pomodori is not quite an appropriate name for the result, since it includes pomodori and breaks.

It would be a good habit to encapsulate your code in a function.

SECOND = SECONDS = 1
MINUTE = MINUTES = 60 * SECONDS
HOUR = HOURS = 60 * MINUTES

POMODORO = 25 * MINUTES
SHORT_BREAK = 5 * MINUTES
LONG_BREAK = 15 * MINUTES

def manage(time):
""" Devise a Pomodoro schedule for a time span, given in seconds. """
schedule = []
while time > 4 * POMODORO + 3 * SHORT_BREAK + LONG_BREAK:
schedule.extend(3 * ['pomodoro', 'short-break'] + ['pomodoro', 'long-break'])
time -= 4 * POMODORO + 3 * SHORT_BREAK + LONG_BREAK
while time > POMODORO + SHORT_BREAK:
schedule.extend(['pomodoro', 'short-break'])
time -= POMODORO + SHORT_BREAK
# 0 < time < POMODORO + SHORT_BREAK
schedule.append('pomodoro')
return schedule

print(manage(3 * HOURS))

-
I would remove the space around your multiplication operators: 4*POMODORO + 3*SHORT_BREAK + LONG_BREAK. This helps give a better visual understanding of groups of operands. –  BeetDemGuise Jun 11 at 11:57
Wow, thank you. That's really nice! This is similar to my first attempt, which I didn't manage to pull off. That's probably the reason I had an iffy feeling about the final code. It's already a method in a project I'm working on, see here. I simplified it a little for this question. Thanks again! :-) –  Matt Deacalion Stevens Jun 11 at 12:34
Please remember to upvote all answers that you find useful. (I recommend waiting a while before accepting an answer, as acceptance usually kills the desire for anyone else to respond.) –  200_success Jun 11 at 12:36