Notes and exercises for learning design patterns
This adapter does not merely rename methods or convert units.
It derives new objects from an existing object:
TemperatureSeries
-> TrainingWindow
-> TrainingWindow
-> TrainingWindow
The source object is one long sequence. The target interface is an iterable of model-ready examples.
last_start = len(series.values) - window_size - horizon + 1
for start in range(max(0, last_start)):
features = tuple(series.values[start:start + window_size])
target_index = start + window_size + horizon - 1
target = series.values[target_index]
windows.append(TrainingWindow(series.sensor_id, features, target))
The formula:
target_index = start + window_size + horizon - 1
means:
skip the feature window
then move horizon steps forward
For horizon=1, the target is the next value.
For horizon=2, the target is two steps after the end of the window.
The client code wants this:
for window in windows:
model.train(window.features, window.target)
The raw object gives this:
series.values
The adapter hides the slicing rules.
The model code does not need to know how TemperatureSeries is stored or how windows are generated.
The most common bug is an off-by-one error.
For example, this is wrong:
last_start = len(series.values) - window_size
It ignores horizon and may produce a target index beyond the end of the series.
The safe version is:
last_start = len(series.values) - window_size - horizon + 1
If window generation becomes expensive and the same series is adapted repeatedly, this adapter can be extended with caching.
That is the topic of Exercise 4.