Temperature log
A day's temperature readings from up to six places: each place's lowest, highest and average, a moving average over 2 to 10 readings, and alerts for readings outside a place's safe range - when a reading comes in and in a list. Temperatures are kept in whole tenths of a degree, so the averages are exact until the display rounds 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's temperature readings from up to six places. For each place it shows the lowest, the highest and the average, a moving average over 2 to 10 readings, and alerts for readings outside the place's safe range: one as soon as such a reading comes in, and a list of all of them on request.
main.eml- the menu, the questions and their checks, and what the screen showsreadings.eml- the places and their readings: finding a place, keeping readings in time order, lowest/highest/total, how far a reading is outside a safe range, and the moving totalsunits.eml- reading and writing temperatures and times, the rounded average, lower case, padding, lists in words
How each part works:
- Temperatures are kept as whole tenths of a degree: 21.5 C is 215. Totals and moving totals are therefore exact, and only the display rounds: an average is the total over the count, rounded to a tenth with halves away from zero (-171.67 tenths is -17.2 C).
- Readings are kept in time order, whatever order they are typed in; a place has at most one reading per minute of the day.
- The moving average slides: each new reading is added to the running total and the reading that leaves the window is taken away. With floating point that running total would drift away from a fresh sum; in whole tenths it is always exactly the sum of the window.
- The lowest and the highest are the earliest when a value comes up again, and across places the first place added wins a tie.
- A safe range runs from a lowest to a highest temperature, both allowed. A reading below or above it is reported with how far outside it is.
What is checked: a place name is 1 to 16 letters, digits, spaces or hyphens, matched without regard to case; there are at most 6 places and 48 readings a place; a time is H:MM or HH:MM from 00:00 to 23:59, not already taken at that place; a temperature is -60.0 to 60.0 with at most one decimal; a safe range's highest is above its lowest; a window is 2 to 10 readings and no more than the place has. An empty answer cancels.
Sessions: sessions/basic.in logs a greenhouse (one reading typed out of order, one place name typed in lower case) and a freezer, sets both safe ranges, gets an alert at 31.5 C, and shows the summary, the moving average of 3 and the alerts, before and after a third place without a range; sessions/bad-input.in asks for a summary, a moving average and alerts with no readings, then types a name with #, times 25:00 and 7:5, temperatures abc, 61, -60.05 and 21.25, a reading at a time that is taken, a moving average with one reading, a place that does not exist, a safe range whose highest is not above its lowest, a seventh place, windows 1 and x, and a place's 49th reading.
Built on the verified corpus cases temperature-alert-system (readings compared with thresholds to raise alerts) and moving-average (a sliding total, and why a running float total drifts from a fresh sum).
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
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 0
Pick a number from 1 to 6.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> seven
Pick a number from 1 to 6.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 3
No readings yet.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 4
No readings yet.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 5
No readings yet.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 2
No places yet; add a reading first.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place>
Cancelled.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Lab #1
A place name is 1 to 16 letters, digits, spaces or hyphens.
place> Lab
time (HH:MM)> 25:00
Type a time from 00:00 to 23:59, such as 07:30.
time (HH:MM)> 7:5
Type a time from 00:00 to 23:59, such as 07:30.
time (HH:MM)> 07:05
temperature (C)> abc
Type a temperature from -60.0 to 60.0 with at most one decimal, such as -3.5 or 21.
temperature (C)> 61
Type a temperature from -60.0 to 60.0 with at most one decimal, such as -3.5 or 21.
temperature (C)> -60.05
Type a temperature from -60.0 to 60.0 with at most one decimal, such as -3.5 or 21.
temperature (C)> 21.25
Type a temperature from -60.0 to 60.0 with at most one decimal, such as -3.5 or 21.
temperature (C)> -0.5
Recorded: Lab 07:05 -0.5 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> lab
time (HH:MM)> 7:05
Lab already has a reading at 07:05 (-0.5 C).
time (HH:MM)>
Cancelled.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 4
place> Lab
A moving average needs at least 2 readings; Lab has 1.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 2
place> Kitchen
There is no place called Kitchen yet; add a reading for it first.
place>
Cancelled.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 2
place> Lab
lowest (C)> 5
highest (C)> 5
The highest has to be above the lowest (5.0 C).
highest (C)> 4.9
The highest has to be above the lowest (5.0 C).
highest (C)>
Cancelled.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 5
No place has a safe range yet; set one with 2.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 12:00
temperature (C)> 10
Recorded: A 12:00 10.0 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> B
time (HH:MM)> 12:00
temperature (C)> 10
Recorded: B 12:00 10.0 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> C
time (HH:MM)> 12:00
temperature (C)> 10
Recorded: C 12:00 10.0 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> D
time (HH:MM)> 12:00
temperature (C)> 10
Recorded: D 12:00 10.0 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> E
time (HH:MM)> 12:00
temperature (C)> 10
Recorded: E 12:00 10.0 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> F
There are already 6 places; a reading has to go to one of them.
place>
Cancelled.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 2
place> Lab
lowest (C)> -1
highest (C)> 1
Lab's safe range: -1.0 to 1.0 C. Every reading so far is inside it.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 5
Every reading is inside its place's safe range.
No safe range yet: A, B, C, D and E.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Lab
time (HH:MM)> 09:00
temperature (C)> 2
Recorded: Lab 09:00 2.0 C.
ALERT: 2.0 C is 1.0 above Lab's safe range of -1.0 to 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 4
place> Lab
window (2 to 2 readings)> 1
Type a whole number from 2 to 2.
window (2 to 2 readings)> x
Type a whole number from 2 to 2.
window (2 to 2 readings)> 2
Lab, moving average of the last 2 readings:
07:05 -0.5
09:00 2.0 average 0.8
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 3
Lab: 2 readings, 07:05 to 09:00
lowest -0.5 C at 07:05, highest 2.0 C at 09:00, average 0.8 C
safe range -1.0 to 1.0 C, 1 reading outside it
A: 1 reading, at 12:00
lowest 10.0 C at 12:00, highest 10.0 C at 12:00, average 10.0 C
no safe range
B: 1 reading, at 12:00
lowest 10.0 C at 12:00, highest 10.0 C at 12:00, average 10.0 C
no safe range
C: 1 reading, at 12:00
lowest 10.0 C at 12:00, highest 10.0 C at 12:00, average 10.0 C
no safe range
D: 1 reading, at 12:00
lowest 10.0 C at 12:00, highest 10.0 C at 12:00, average 10.0 C
no safe range
E: 1 reading, at 12:00
lowest 10.0 C at 12:00, highest 10.0 C at 12:00, average 10.0 C
no safe range
All places: 7 readings; lowest -0.5 C (Lab, 07:05), highest 10.0 C (A, 12:00).
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 00:00
temperature (C)> 0
Recorded: A 00:00 0.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 00:30
temperature (C)> 1
Recorded: A 00:30 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 01:00
temperature (C)> 2
Recorded: A 01:00 2.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 01:30
temperature (C)> 3
Recorded: A 01:30 3.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 02:00
temperature (C)> 4
Recorded: A 02:00 4.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 02:30
temperature (C)> 5
Recorded: A 02:30 5.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 03:00
temperature (C)> 6
Recorded: A 03:00 6.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 03:30
temperature (C)> 0
Recorded: A 03:30 0.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 04:00
temperature (C)> 1
Recorded: A 04:00 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 04:30
temperature (C)> 2
Recorded: A 04:30 2.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 05:00
temperature (C)> 3
Recorded: A 05:00 3.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 05:30
temperature (C)> 4
Recorded: A 05:30 4.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 06:00
temperature (C)> 5
Recorded: A 06:00 5.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 06:30
temperature (C)> 6
Recorded: A 06:30 6.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 07:00
temperature (C)> 0
Recorded: A 07:00 0.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 07:30
temperature (C)> 1
Recorded: A 07:30 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 08:00
temperature (C)> 2
Recorded: A 08:00 2.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 08:30
temperature (C)> 3
Recorded: A 08:30 3.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 09:00
temperature (C)> 4
Recorded: A 09:00 4.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 09:30
temperature (C)> 5
Recorded: A 09:30 5.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 10:00
temperature (C)> 6
Recorded: A 10:00 6.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 10:30
temperature (C)> 0
Recorded: A 10:30 0.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 11:00
temperature (C)> 1
Recorded: A 11:00 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 11:30
temperature (C)> 2
Recorded: A 11:30 2.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 12:30
temperature (C)> 4
Recorded: A 12:30 4.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 13:00
temperature (C)> 5
Recorded: A 13:00 5.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 13:30
temperature (C)> 6
Recorded: A 13:30 6.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 14:00
temperature (C)> 0
Recorded: A 14:00 0.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 14:30
temperature (C)> 1
Recorded: A 14:30 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 15:00
temperature (C)> 2
Recorded: A 15:00 2.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 15:30
temperature (C)> 3
Recorded: A 15:30 3.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 16:00
temperature (C)> 4
Recorded: A 16:00 4.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 16:30
temperature (C)> 5
Recorded: A 16:30 5.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 17:00
temperature (C)> 6
Recorded: A 17:00 6.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 17:30
temperature (C)> 0
Recorded: A 17:30 0.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 18:00
temperature (C)> 1
Recorded: A 18:00 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 18:30
temperature (C)> 2
Recorded: A 18:30 2.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 19:00
temperature (C)> 3
Recorded: A 19:00 3.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 19:30
temperature (C)> 4
Recorded: A 19:30 4.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 20:00
temperature (C)> 5
Recorded: A 20:00 5.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 20:30
temperature (C)> 6
Recorded: A 20:30 6.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 21:00
temperature (C)> 0
Recorded: A 21:00 0.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 21:30
temperature (C)> 1
Recorded: A 21:30 1.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 22:00
temperature (C)> 2
Recorded: A 22:00 2.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 22:30
temperature (C)> 3
Recorded: A 22:30 3.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 23:00
temperature (C)> 4
Recorded: A 23:00 4.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
time (HH:MM)> 23:30
temperature (C)> 5
Recorded: A 23:30 5.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> A
A already has 48 readings, the most a place keeps.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 6
Bye.
What was typed (264 lines)
0
seven
3
4
5
2
1
1
Lab #1
Lab
25:00
7:5
07:05
abc
61
-60.05
21.25
-0.5
1
lab
7:05
4
Lab
2
Kitchen
2
Lab
5
5
4.9
5
1
A
12:00
10
1
B
12:00
10
1
C
12:00
10
1
D
12:00
10
1
E
12:00
10
1
F
2
Lab
-1
1
5
1
Lab
09:00
2
4
Lab
1
x
2
3
1
A
00:00
0
1
A
00:30
1
1
A
01:00
2
1
A
01:30
3
1
A
02:00
4
1
A
02:30
5
1
A
03:00
6
1
A
03:30
0
1
A
04:00
1
1
A
04:30
2
1
A
05:00
3
1
A
05:30
4
1
A
06:00
5
1
A
06:30
6
1
A
07:00
0
1
A
07:30
1
1
A
08:00
2
1
A
08:30
3
1
A
09:00
4
1
A
09:30
5
1
A
10:00
6
1
A
10:30
0
1
A
11:00
1
1
A
11:30
2
1
A
12:30
4
1
A
13:00
5
1
A
13:30
6
1
A
14:00
0
1
A
14:30
1
1
A
15:00
2
1
A
15:30
3
1
A
16:00
4
1
A
16:30
5
1
A
17:00
6
1
A
17:30
0
1
A
18:00
1
1
A
18:30
2
1
A
19:00
3
1
A
19:30
4
1
A
20:00
5
1
A
20:30
6
1
A
21:00
0
1
A
21:30
1
1
A
22:00
2
1
A
22:30
3
1
A
23:00
4
1
A
23:30
5
1
A
6
basic
interpreter: byte-equal
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Greenhouse
time (HH:MM)> 06:30
temperature (C)> 12.5
Recorded: Greenhouse 06:30 12.5 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Greenhouse
time (HH:MM)> 8:00
temperature (C)> 15
Recorded: Greenhouse 08:00 15.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> greenhouse
time (HH:MM)> 14:00
temperature (C)> 28.0
Recorded: Greenhouse 14:00 28.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Greenhouse
time (HH:MM)> 10:00
temperature (C)> 19
Recorded: Greenhouse 10:00 19.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Freezer
time (HH:MM)> 08:00
temperature (C)> -21
Recorded: Freezer 08:00 -21.0 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Freezer
time (HH:MM)> 14:00
temperature (C)> -12
Recorded: Freezer 14:00 -12.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Freezer
time (HH:MM)> 11:30
temperature (C)> -18.5
Recorded: Freezer 11:30 -18.5 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 2
place> Greenhouse
lowest (C)> 15
highest (C)> 30
Greenhouse's safe range: 15.0 to 30.0 C. 1 of its 4 readings is outside it.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 2
place> Freezer
lowest (C)> -25
highest (C)> -15
Freezer's safe range: -25.0 to -15.0 C. 1 of its 3 readings is outside it.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Greenhouse
time (HH:MM)> 16:30
temperature (C)> 31.5
Recorded: Greenhouse 16:30 31.5 C.
ALERT: 31.5 C is 1.5 above Greenhouse's safe range of 15.0 to 30.0 C.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 3
Greenhouse: 5 readings, 06:30 to 16:30
lowest 12.5 C at 06:30, highest 31.5 C at 16:30, average 21.2 C
safe range 15.0 to 30.0 C, 2 readings outside it
Freezer: 3 readings, 08:00 to 14:00
lowest -21.0 C at 08:00, highest -12.0 C at 14:00, average -17.2 C
safe range -25.0 to -15.0 C, 1 reading outside it
All places: 8 readings; lowest -21.0 C (Freezer, 08:00), highest 31.5 C (Greenhouse, 16:30).
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 4
place> Greenhouse
window (2 to 5 readings)> 3
Greenhouse, moving average of the last 3 readings:
06:30 12.5
08:00 15.0
10:00 19.0 average 15.5
14:00 28.0 average 20.7
16:30 31.5 average 26.2
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 5
Outside the safe ranges:
Greenhouse 06:30 12.5 C, 2.5 below 15.0
Greenhouse 16:30 31.5 C, 1.5 above 30.0
Freezer 14:00 -12.0 C, 3.0 above -15.0
3 of 8 readings at places with a safe range are outside it.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 1
place> Hall
time (HH:MM)> 12:00
temperature (C)> 21.5
Recorded: Hall 12:00 21.5 C - a new place.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 5
Outside the safe ranges:
Greenhouse 06:30 12.5 C, 2.5 below 15.0
Greenhouse 16:30 31.5 C, 1.5 above 30.0
Freezer 14:00 -12.0 C, 3.0 above -15.0
3 of 8 readings at places with a safe range are outside it.
No safe range yet: Hall.
== Temperature log ==
1) add a reading 2) set a safe range 3) summary
4) moving average 5) alerts 6) quit
choice> 6
Bye.
What was typed (51 lines)
1
Greenhouse
06:30
12.5
1
Greenhouse
8:00
15
1
greenhouse
14:00
28.0
1
Greenhouse
10:00
19
1
Freezer
08:00
-21
1
Freezer
14:00
-12
1
Freezer
11:30
-18.5
2
Greenhouse
15
30
2
Freezer
-25
-15
1
Greenhouse
16:30
31.5
3
4
Greenhouse
3
5
1
Hall
12:00
21.5
5
6
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# P026 temperature log: readings from several places over one day, each
# place's lowest, highest and average, a moving average, and alerts for
# readings outside a place's safe range.
import readings
import units
6 => most_places
48 => most_readings
def good_name(s):
# 1 to 16 letters, digits, spaces or hyphens.
if s == "" or len(s) > 16:
return False
for c in s:
if not (c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 -"):
return False
return True
def ask_place(places, must_exist):
# [index, name]: the index of a known place, or len(places) for a new one
# (only when must_exist is False); [-1, ""] when the answer is empty,
# which cancels.
while True:
units.trim(input("place> ")) => name
if name == "":
return [0 - 1, ""]
readings.find(places, name) => i
if not good_name(name):
"A place name is 1 to 16 letters, digits, spaces or hyphens." ^0
elif i >= 0:
return [i, places[i][0]]
elif must_exist:
("There is no place called " + name + " yet; add a reading for it first.") ^0
elif len(places) >= most_places:
("There are already " + str(most_places) + " places; a reading has to go to one of them.") ^0
else:
return [len(places), name]
def ask_temperature(prompt):
# [True, tenths], or [False, 0] when the answer is empty, which cancels.
while True:
input(prompt) => answer
if units.trim(answer) == "":
return [False, 0]
units.temperature(answer) => t
if t[0]:
return t
"Type a temperature from -60.0 to 60.0 with at most one decimal, such as -3.5 or 21." ^0
def range_text(place):
return units.show(place[3]) + " to " + units.show(place[4]) + " C"
def add_reading(places):
# Returns the places, with a new one if the reading started a place.
ask_place(places, False) => answer
answer[0] => i
if i == 0 - 1:
"Cancelled." ^0
return places
if i < len(places) and len(places[i][1]) >= most_readings:
(places[i][0] + " already has " + str(most_readings) + " readings, the most a place keeps.") ^0
return places
0 - 1 => minute
while minute == 0 - 1:
input("time (HH:MM)> ") => typed
if units.trim(typed) == "":
"Cancelled." ^0
return places
units.minute_of(typed) => minute
if minute == 0 - 1:
"Type a time from 00:00 to 23:59, such as 07:30." ^0
elif i < len(places) and readings.reading_at(places[i][1], minute) >= 0:
places[i][1][readings.reading_at(places[i][1], minute)] => there
(places[i][0] + " already has a reading at " + units.clock(minute) + " (" + units.show(there[1]) + " C).") ^0
0 - 1 => minute
ask_temperature("temperature (C)> ") => t
if not t[0]:
"Cancelled." ^0
return places
if i == len(places):
places + [[answer[1], [[minute, t[1]]], False, 0, 0]] => places
places[i] => place
("Recorded: " + place[0] + " " + units.clock(minute) + " " + units.show(t[1]) + " C - a new place.") ^0
else:
places[i] => place
readings.with_reading(place[1], minute, t[1]) => place[1]
("Recorded: " + place[0] + " " + units.clock(minute) + " " + units.show(t[1]) + " C.") ^0
readings.off_range(place, t[1]) => off
if off < 0:
("ALERT: " + units.show(t[1]) + " C is " + units.show(0 - off) + " below " + place[0] + "'s safe range of " + range_text(place) + ".") ^0
elif off > 0:
("ALERT: " + units.show(t[1]) + " C is " + units.show(off) + " above " + place[0] + "'s safe range of " + range_text(place) + ".") ^0
return places
def set_range(places):
if len(places) == 0:
"No places yet; add a reading first." ^0
return
ask_place(places, True)[0] => i
if i == 0 - 1:
"Cancelled." ^0
return
places[i] => place
ask_temperature("lowest (C)> ") => low
if not low[0]:
"Cancelled." ^0
return
[False, 0] => high
while not high[0]:
ask_temperature("highest (C)> ") => high
if not high[0]:
"Cancelled." ^0
return
if high[1] <= low[1]:
("The highest has to be above the lowest (" + units.show(low[1]) + " C).") ^0
[False, 0] => high
True => place[2]
low[1] => place[3]
high[1] => place[4]
readings.count_outside(place) => n
len(place[1]) => total
if n == 0:
"Every reading so far is inside it." => message
elif n == total:
"Every reading so far is outside it." => message
elif n == 1:
("1 of its " + str(total) + " readings is outside it.") => message
else:
(str(n) + " of its " + str(total) + " readings are outside it.") => message
(place[0] + "'s safe range: " + range_text(place) + ". " + message) ^0
def summary(places):
if len(places) == 0:
"No readings yet." ^0
return
0 => all_count
for place in places:
readings.stats(place[1]) => s
place[1] => rs
if s[0] == 1:
(place[0] + ": 1 reading, at " + units.clock(rs[0][0])) ^0
else:
(place[0] + ": " + str(s[0]) + " readings, " + units.clock(rs[0][0]) + " to " + units.clock(rs[len(rs) - 1][0])) ^0
(" lowest " + units.show(s[1]) + " C at " + units.clock(s[2]) + ", highest " + units.show(s[3]) + " C at " + units.clock(s[4]) + ", average " + units.show(units.average(s[5], s[0])) + " C") ^0
if place[2]:
(" safe range " + range_text(place) + ", " + units.plural(readings.count_outside(place), "reading") + " outside it") ^0
else:
" no safe range" ^0
all_count + s[0] => all_count
if len(places) > 1:
readings.stats(places[0][1]) => first
first[1] => low
first[2] => low_at
places[0][0] => low_place
first[3] => high
first[4] => high_at
places[0][0] => high_place
for place in places:
readings.stats(place[1]) => s
if s[1] < low:
s[1] => low
s[2] => low_at
place[0] => low_place
if s[3] > high:
s[3] => high
s[4] => high_at
place[0] => high_place
("All places: " + str(all_count) + " readings; lowest " + units.show(low) + " C (" + low_place + ", " + units.clock(low_at) + "), highest " + units.show(high) + " C (" + high_place + ", " + units.clock(high_at) + ").") ^0
def moving_average(places):
if len(places) == 0:
"No readings yet." ^0
return
ask_place(places, True)[0] => i
if i == 0 - 1:
"Cancelled." ^0
return
places[i] => place
len(place[1]) => count
if count < 2:
("A moving average needs at least 2 readings; " + place[0] + " has 1.") ^0
return
count => widest
if widest > 10:
10 => widest
0 => window
while window == 0:
input("window (2 to " + str(widest) + " readings)> ") => typed
if units.trim(typed) == "":
"Cancelled." ^0
return
units.whole_number(typed) => window
if window < 2 or window > widest:
("Type a whole number from 2 to " + str(widest) + ".") ^0
0 => window
(place[0] + ", moving average of the last " + str(window) + " readings:") ^0
for row in readings.moving(place[1], window):
(" " + units.clock(row[0]) + units.pad_left(units.show(row[1]), 7)) => line
if row[2] != None:
line + " average " + units.show(units.average(row[2], window)) => line
line ^0
def alerts(places):
if len(places) == 0:
"No readings yet." ^0
return
[] => lines
0 => checked
[] => unranged
for place in places:
if place[2]:
for r in place[1]:
readings.off_range(place, r[1]) => off
checked + 1 => checked
if off < 0:
lines + [" " + place[0] + " " + units.clock(r[0]) + " " + units.show(r[1]) + " C, " + units.show(0 - off) + " below " + units.show(place[3])] => lines
elif off > 0:
lines + [" " + place[0] + " " + units.clock(r[0]) + " " + units.show(r[1]) + " C, " + units.show(off) + " above " + units.show(place[4])] => lines
else:
unranged + [place[0]] => unranged
if checked == 0:
"No place has a safe range yet; set one with 2." ^0
return
if len(lines) == 0:
"Every reading is inside its place's safe range." ^0
else:
"Outside the safe ranges:" ^0
for line in lines:
line ^0
if len(lines) == 1:
("1 of " + units.plural(checked, "reading") + " at places with a safe range is outside it.") ^0
else:
(str(len(lines)) + " of " + units.plural(checked, "reading") + " at places with a safe range are outside it.") ^0
if len(unranged) > 0:
("No safe range yet: " + units.listed(unranged) + ".") ^0
[] => places
True => running
while running:
"" ^0
"== Temperature log ==" ^0
"1) add a reading 2) set a safe range 3) summary" ^0
"4) moving average 5) alerts 6) quit" ^0
units.trim(input("choice> ")) => choice
if choice == "1":
add_reading(places) => places
elif choice == "2":
set_range(places)
elif choice == "3":
summary(places)
elif choice == "4":
moving_average(places)
elif choice == "5":
alerts(places)
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import readings
import units
most_places = 6
most_readings = 48
def good_name(s):
if s == "" or len(s) > 16:
return False
for c in s:
if not c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 -":
return False
return True
def ask_place(places, must_exist):
while True:
name = units.trim(input("place> "))
if name == "":
return [0 - 1, ""]
i = readings.find(places, name)
if not good_name(name):
print("A place name is 1 to 16 letters, digits, spaces or hyphens.")
elif i >= 0:
return [i, places[i][0]]
elif must_exist:
print("There is no place called " + name + " yet; add a reading for it first.")
elif len(places) >= most_places:
print("There are already " + str(most_places) + " places; a reading has to go to one of them.")
else:
return [len(places), name]
def ask_temperature(prompt):
while True:
answer = input(prompt)
if units.trim(answer) == "":
return [False, 0]
t = units.temperature(answer)
if t[0]:
return t
print("Type a temperature from -60.0 to 60.0 with at most one decimal, such as -3.5 or 21.")
def range_text(place):
return units.show(place[3]) + " to " + units.show(place[4]) + " C"
def add_reading(places):
answer = ask_place(places, False)
i = answer[0]
if i == 0 - 1:
print("Cancelled.")
return places
if i < len(places) and len(places[i][1]) >= most_readings:
print(places[i][0] + " already has " + str(most_readings) + " readings, the most a place keeps.")
return places
minute = 0 - 1
while minute == 0 - 1:
typed = input("time (HH:MM)> ")
if units.trim(typed) == "":
print("Cancelled.")
return places
minute = units.minute_of(typed)
if minute == 0 - 1:
print("Type a time from 00:00 to 23:59, such as 07:30.")
elif i < len(places) and readings.reading_at(places[i][1], minute) >= 0:
there = places[i][1][readings.reading_at(places[i][1], minute)]
print(places[i][0] + " already has a reading at " + units.clock(minute) + " (" + units.show(there[1]) + " C).")
minute = 0 - 1
t = ask_temperature("temperature (C)> ")
if not t[0]:
print("Cancelled.")
return places
if i == len(places):
places = places + [[answer[1], [[minute, t[1]]], False, 0, 0]]
place = places[i]
print("Recorded: " + place[0] + " " + units.clock(minute) + " " + units.show(t[1]) + " C - a new place.")
else:
place = places[i]
place[1] = readings.with_reading(place[1], minute, t[1])
print("Recorded: " + place[0] + " " + units.clock(minute) + " " + units.show(t[1]) + " C.")
off = readings.off_range(place, t[1])
if off < 0:
print("ALERT: " + units.show(t[1]) + " C is " + units.show(0 - off) + " below " + place[0] + "'s safe range of " + range_text(place) + ".")
elif off > 0:
print("ALERT: " + units.show(t[1]) + " C is " + units.show(off) + " above " + place[0] + "'s safe range of " + range_text(place) + ".")
return places
def set_range(places):
if len(places) == 0:
print("No places yet; add a reading first.")
return
i = ask_place(places, True)[0]
if i == 0 - 1:
print("Cancelled.")
return
place = places[i]
low = ask_temperature("lowest (C)> ")
if not low[0]:
print("Cancelled.")
return
high = [False, 0]
while not high[0]:
high = ask_temperature("highest (C)> ")
if not high[0]:
print("Cancelled.")
return
if high[1] <= low[1]:
print("The highest has to be above the lowest (" + units.show(low[1]) + " C).")
high = [False, 0]
place[2] = True
place[3] = low[1]
place[4] = high[1]
n = readings.count_outside(place)
total = len(place[1])
if n == 0:
message = "Every reading so far is inside it."
elif n == total:
message = "Every reading so far is outside it."
elif n == 1:
message = "1 of its " + str(total) + " readings is outside it."
else:
message = str(n) + " of its " + str(total) + " readings are outside it."
print(place[0] + "'s safe range: " + range_text(place) + ". " + message)
def summary(places):
if len(places) == 0:
print("No readings yet.")
return
all_count = 0
for place in places:
s = readings.stats(place[1])
rs = place[1]
if s[0] == 1:
print(place[0] + ": 1 reading, at " + units.clock(rs[0][0]))
else:
print(place[0] + ": " + str(s[0]) + " readings, " + units.clock(rs[0][0]) + " to " + units.clock(rs[len(rs) - 1][0]))
print(" lowest " + units.show(s[1]) + " C at " + units.clock(s[2]) + ", highest " + units.show(s[3]) + " C at " + units.clock(s[4]) + ", average " + units.show(units.average(s[5], s[0])) + " C")
if place[2]:
print(" safe range " + range_text(place) + ", " + units.plural(readings.count_outside(place), "reading") + " outside it")
else:
print(" no safe range")
all_count = all_count + s[0]
if len(places) > 1:
first = readings.stats(places[0][1])
low = first[1]
low_at = first[2]
low_place = places[0][0]
high = first[3]
high_at = first[4]
high_place = places[0][0]
for place in places:
s = readings.stats(place[1])
if s[1] < low:
low = s[1]
low_at = s[2]
low_place = place[0]
if s[3] > high:
high = s[3]
high_at = s[4]
high_place = place[0]
print("All places: " + str(all_count) + " readings; lowest " + units.show(low) + " C (" + low_place + ", " + units.clock(low_at) + "), highest " + units.show(high) + " C (" + high_place + ", " + units.clock(high_at) + ").")
def moving_average(places):
if len(places) == 0:
print("No readings yet.")
return
i = ask_place(places, True)[0]
if i == 0 - 1:
print("Cancelled.")
return
place = places[i]
count = len(place[1])
if count < 2:
print("A moving average needs at least 2 readings; " + place[0] + " has 1.")
return
widest = count
if widest > 10:
widest = 10
window = 0
while window == 0:
typed = input("window (2 to " + str(widest) + " readings)> ")
if units.trim(typed) == "":
print("Cancelled.")
return
window = units.whole_number(typed)
if window < 2 or window > widest:
print("Type a whole number from 2 to " + str(widest) + ".")
window = 0
print(place[0] + ", moving average of the last " + str(window) + " readings:")
for row in readings.moving(place[1], window):
line = " " + units.clock(row[0]) + units.pad_left(units.show(row[1]), 7)
if row[2] != None:
line = line + " average " + units.show(units.average(row[2], window))
print(line)
def alerts(places):
if len(places) == 0:
print("No readings yet.")
return
lines = []
checked = 0
unranged = []
for place in places:
if place[2]:
for r in place[1]:
off = readings.off_range(place, r[1])
checked = checked + 1
if off < 0:
lines = lines + [" " + place[0] + " " + units.clock(r[0]) + " " + units.show(r[1]) + " C, " + units.show(0 - off) + " below " + units.show(place[3])]
elif off > 0:
lines = lines + [" " + place[0] + " " + units.clock(r[0]) + " " + units.show(r[1]) + " C, " + units.show(off) + " above " + units.show(place[4])]
else:
unranged = unranged + [place[0]]
if checked == 0:
print("No place has a safe range yet; set one with 2.")
return
if len(lines) == 0:
print("Every reading is inside its place's safe range.")
else:
print("Outside the safe ranges:")
for line in lines:
print(line)
if len(lines) == 1:
print("1 of " + units.plural(checked, "reading") + " at places with a safe range is outside it.")
else:
print(str(len(lines)) + " of " + units.plural(checked, "reading") + " at places with a safe range are outside it.")
if len(unranged) > 0:
print("No safe range yet: " + units.listed(unranged) + ".")
places = []
running = True
while running:
print("")
print("== Temperature log ==")
print("1) add a reading 2) set a safe range 3) summary")
print("4) moving average 5) alerts 6) quit")
choice = units.trim(input("choice> "))
if choice == "1":
places = add_reading(places)
elif choice == "2":
set_range(places)
elif choice == "3":
summary(places)
elif choice == "4":
moving_average(places)
elif choice == "5":
alerts(places)
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
readings.eml
eml# P026 temperature log - the places and their readings. A place is
# [name, readings, has_range, lowest, highest]; its readings are
# [minute, tenths] pairs kept in time order, one per minute of the day.
import units
def find(places, name):
# The index of the place with this name, ignoring case; -1 if none.
units.lower(name) => wanted
0 => i
while i < len(places):
if units.lower(places[i][0]) == wanted:
return i
i + 1 => i
return 0 - 1
def reading_at(readings, minute):
# The index of the reading taken at this minute; -1 if none.
0 => i
while i < len(readings):
if readings[i][0] == minute:
return i
i + 1 => i
return 0 - 1
def with_reading(readings, minute, tenths):
# The readings with one more, still in time order.
0 => k
while k < len(readings) and readings[k][0] < minute:
k + 1 => k
return readings[0:k] + [[minute, tenths]] + readings[k:len(readings)]
def stats(readings):
# [count, lowest, its minute, highest, its minute, total]; the earliest
# reading counts when a lowest or highest value comes up again.
readings[0] => first
first[1] => low
first[0] => low_at
first[1] => high
first[0] => high_at
0 => total
for r in readings:
if r[1] < low:
r[1] => low
r[0] => low_at
if r[1] > high:
r[1] => high
r[0] => high_at
total + r[1] => total
return [len(readings), low, low_at, high, high_at, total]
def off_range(place, tenths):
# How far a reading is outside the place's safe range: below is negative,
# above is positive, inside (or no range) is 0.
if not place[2]:
return 0
if tenths < place[3]:
return tenths - place[3]
if tenths > place[4]:
return tenths - place[4]
return 0
def count_outside(place):
0 => n
for r in place[1]:
if off_range(place, r[1]) != 0:
n + 1 => n
return n
def moving(readings, window):
# For each reading [minute, tenths, total of the last `window` readings or
# None while fewer have come]. The total slides: the entering reading is
# added and the leaving one taken away, in whole tenths, so it never drifts.
[] => out
0 => total
0 => i
while i < len(readings):
total + readings[i][1] => total
if i >= window:
total - readings[i - window][1] => total
if i >= window - 1:
out + [[readings[i][0], readings[i][1], total]] => out
else:
out + [[readings[i][0], readings[i][1], None]] => out
i + 1 => i
return out
Python projection (readings.py)
import units
def find(places, name):
wanted = units.lower(name)
i = 0
while i < len(places):
if units.lower(places[i][0]) == wanted:
return i
i = i + 1
return 0 - 1
def reading_at(readings, minute):
i = 0
while i < len(readings):
if readings[i][0] == minute:
return i
i = i + 1
return 0 - 1
def with_reading(readings, minute, tenths):
k = 0
while k < len(readings) and readings[k][0] < minute:
k = k + 1
return readings[0:k] + [[minute, tenths]] + readings[k:len(readings)]
def stats(readings):
first = readings[0]
low = first[1]
low_at = first[0]
high = first[1]
high_at = first[0]
total = 0
for r in readings:
if r[1] < low:
low = r[1]
low_at = r[0]
if r[1] > high:
high = r[1]
high_at = r[0]
total = total + r[1]
return [len(readings), low, low_at, high, high_at, total]
def off_range(place, tenths):
if not place[2]:
return 0
if tenths < place[3]:
return tenths - place[3]
if tenths > place[4]:
return tenths - place[4]
return 0
def count_outside(place):
n = 0
for r in place[1]:
if off_range(place, r[1]) != 0:
n = n + 1
return n
def moving(readings, window):
out = []
total = 0
i = 0
while i < len(readings):
total = total + readings[i][1]
if i >= window:
total = total - readings[i - window][1]
if i >= window - 1:
out = out + [[readings[i][0], readings[i][1], total]]
else:
out = out + [[readings[i][0], readings[i][1], None]]
i = i + 1
return out
units.eml
eml# P026 temperature log - reading and writing temperatures and times.
# Temperatures are kept as whole tenths of a degree (21.5 C is 215), so sums
# and averages are exact; only the display rounds. The interpreter that checks
# every session does not run string methods yet, so the text handling is
# written out here.
"0123456789" => digits
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 lower(s):
# s with A-Z as a-z.
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" => upper
"abcdefghijklmnopqrstuvwxyz" => small
"" => out
for c in s:
0 => k
while k < 26 and upper[k] != c:
k + 1 => k
if k < 26:
out + small[k] => out
else:
out + c => out
return out
def quotient(a, b):
# a divided by b, rounded down, for a >= 0 and b > 0.
return int((a - a % b) / b)
def average(total, count):
# total / count rounded to a whole number, halves away from zero.
if total < 0:
return 0 - quotient(0 - 2 * total + count, 2 * count)
return quotient(2 * total + count, 2 * count)
def show(tenths):
# 215 as "21.5", -5 as "-0.5", 0 as "0.0".
"" => sign
tenths => a
if a < 0:
"-" => sign
0 - a => a
return sign + str(quotient(a, 10)) + "." + str(a % 10)
def temperature(s):
# [True, tenths] for "21", "21.5", "-3.5" or "+4" from -60.0 to 60.0 with
# at most one decimal; [False, 0] for anything else.
trim(s) => s
1 => sign
if s != "" and (s[0] == "-" or s[0] == "+"):
if s[0] == "-":
0 - 1 => sign
s[1:len(s)] => s
0 => whole
0 => n
while n < len(s) and s[n] in digits:
whole * 10 + int(s[n]) => whole
n + 1 => n
if n == 0 or n > 2:
return [False, 0]
0 => tenth
if n < len(s):
if s[n] != "." or n + 2 != len(s) or not (s[n + 1] in digits):
return [False, 0]
int(s[n + 1]) => tenth
sign * (whole * 10 + tenth) => t
if t < 0 - 600 or t > 600:
return [False, 0]
return [True, t]
def minute_of(s):
# Minutes after midnight for "7:05" or "07:05" (00:00 to 23:59), else -1.
trim(s) => s
0 => colon
while colon < len(s) and s[colon] != ":":
colon + 1 => colon
if colon == 0 or colon > 2 or colon + 3 != len(s):
return 0 - 1
0 => h
for c in s[0:colon]:
if not (c in digits):
return 0 - 1
h * 10 + int(c) => h
if not (s[colon + 1] in digits and s[colon + 2] in digits):
return 0 - 1
int(s[colon + 1]) * 10 + int(s[colon + 2]) => m
if h > 23 or m > 59:
return 0 - 1
return h * 60 + m
def clock(minute):
# 425 as "07:05".
quotient(minute, 60) => h
minute % 60 => m
"" => out
if h < 10:
"0" => out
out + str(h) + ":" => out
if m < 10:
out + "0" => out
return out + str(m)
def pad_left(s, width):
while len(s) < width:
" " + s => s
return s
def plural(n, word):
if n == 1:
return "1 " + word
return str(n) + " " + word + "s"
def whole_number(s):
# The value of 1 to 3 digits; -1 for anything else.
trim(s) => s
if s == "" or len(s) > 3:
return 0 - 1
0 => n
for c in s:
if not (c in digits):
return 0 - 1
n * 10 + int(c) => n
return n
def listed(items):
# ["a", "b", "c"] as "a, b and c".
"" => out
0 => i
while i < len(items):
if i > 0 and i == len(items) - 1:
out + " and " => out
elif i > 0:
out + ", " => out
out + items[i] => out
i + 1 => i
return out
Python projection (units.py)
digits = "0123456789"
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 lower(s):
upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
small = "abcdefghijklmnopqrstuvwxyz"
out = ""
for c in s:
k = 0
while k < 26 and upper[k] != c:
k = k + 1
if k < 26:
out = out + small[k]
else:
out = out + c
return out
def quotient(a, b):
return int((a - a % b) / b)
def average(total, count):
if total < 0:
return 0 - quotient(0 - 2 * total + count, 2 * count)
return quotient(2 * total + count, 2 * count)
def show(tenths):
sign = ""
a = tenths
if a < 0:
sign = "-"
a = 0 - a
return sign + str(quotient(a, 10)) + "." + str(a % 10)
def temperature(s):
s = trim(s)
sign = 1
if s != "" and (s[0] == "-" or s[0] == "+"):
if s[0] == "-":
sign = 0 - 1
s = s[1:len(s)]
whole = 0
n = 0
while n < len(s) and s[n] in digits:
whole = whole * 10 + int(s[n])
n = n + 1
if n == 0 or n > 2:
return [False, 0]
tenth = 0
if n < len(s):
if s[n] != "." or n + 2 != len(s) or not s[n + 1] in digits:
return [False, 0]
tenth = int(s[n + 1])
t = sign * (whole * 10 + tenth)
if t < 0 - 600 or t > 600:
return [False, 0]
return [True, t]
def minute_of(s):
s = trim(s)
colon = 0
while colon < len(s) and s[colon] != ":":
colon = colon + 1
if colon == 0 or colon > 2 or colon + 3 != len(s):
return 0 - 1
h = 0
for c in s[0:colon]:
if not c in digits:
return 0 - 1
h = h * 10 + int(c)
if not (s[colon + 1] in digits and s[colon + 2] in digits):
return 0 - 1
m = int(s[colon + 1]) * 10 + int(s[colon + 2])
if h > 23 or m > 59:
return 0 - 1
return h * 60 + m
def clock(minute):
h = quotient(minute, 60)
m = minute % 60
out = ""
if h < 10:
out = "0"
out = out + str(h) + ":"
if m < 10:
out = out + "0"
return out + str(m)
def pad_left(s, width):
while len(s) < width:
s = " " + s
return s
def plural(n, word):
if n == 1:
return "1 " + word
return str(n) + " " + word + "s"
def whole_number(s):
s = trim(s)
if s == "" or len(s) > 3:
return 0 - 1
n = 0
for c in s:
if not c in digits:
return 0 - 1
n = n * 10 + int(c)
return n
def listed(items):
out = ""
i = 0
while i < len(items):
if i > 0 and i == len(items) - 1:
out = out + " and "
elif i > 0:
out = out + ", "
out = out + items[i]
i = i + 1
return out