Traceback (most recent call last):
File "readings.py", line 9, in <module>
print(daily_changes([12, 15, 11, 18]))
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "readings.py", line 5, in daily_changes
changes.append(readings[i + 1] - readings[i])
~~~~~~~~^^^^^^^
IndexError: list index out of rangeThe same shape of loop on letters, printing as it goes, so you can see the pass that fails instead of guessing at it.
letters = ["a", "b", "c"]
for i in range(len(letters)):
print(i, letters[i], letters[i + 1])
# 0 a b
# 1 b c
# IndexError: list index out of range <- i is 2, and letters[3] does not exist
- The carets say which subscript
In a line with two indexes, the ~~~^^^ markers under the line show which one failed. Here that is always the i + 1 side, the one that reaches ahead.
- It worked until it did not
Two lines printed before the crash, so the loop body is fine in general. Something is wrong only at the end, and the end is where i + 1 equals the length of the list.
- Count the pairs, not the items
Three letters have two neighbouring pairs. A loop over pairs should run one fewer time than a loop over items, and range(len(letters)) runs one too many.
The loop runs once for every reading. How many changes are there between four readings?
On the last pass, i is len(readings) - 1, so i + 1 is len(readings), one past the end. The loop needs to stop one pass earlier.
Either loop over range(len(readings) - 1), or pair each reading with the next one directly: zip(readings, readings[1:]) gives (earlier, later) pairs and stops by itself.
def daily_changes(readings): return [later - earlier for earlier, later in zip(readings, readings[____:])]
def daily_changes(readings):
"""How much each reading rose or fell from the one before it."""
return [later - earlier for earlier, later in zip(readings, readings[1:])]
readings[1:] is one shorter than readings, so zip produces exactly one pair per neighbour and stops. There is no index to get wrong, so the off-by-one cannot come back.
With one reading or none, readings[1:] is empty, zip yields nothing, and the result is an empty list. No special case needed.
The minimal fix to the starter is changing the range. It works; it just keeps an index you have to reason about, which is how the bug got in the first time.
For long series of numbers, a library does this in one call: numpy.diff for arrays, Series.diff in pandas. Both return the differences with the same n - 1 length rule (pandas keeps the length and puts NaN first instead).
- Return percentage changes instead, and decide what a change from 0 should be.
- Find the longest run of days that each rose on the day before.
- Do it with itertools.pairwise and compare how it reads with zip.