Case Study 1: Analyzing Weather Data
The Scenario
The Clearwater Regional Weather Service has collected daily high temperature readings for an entire year — 365 values measured in degrees Fahrenheit. They need a program to answer several questions:
- What was the average temperature for each month?
- Which day was the hottest? The coldest?
- Were there any heat waves? (Defined as 3 or more consecutive days at or above 95 degrees F.)
- How many days were below freezing (32 degrees F or lower)?
- What is the overall temperature distribution by decade (30s, 40s, 50s, ..., 100s)?
This case study demonstrates how a single array of 365 temperatures, combined with the algorithms from this chapter, can answer a rich set of analytical questions.
Data Design
We need the daily temperatures plus a way to know which month each day belongs to:
program WeatherAnalysis;
const
DAYS_IN_YEAR = 365;
type
TTemperatureArray = array[1..DAYS_IN_YEAR] of Real;
TMonthlyAverages = array[1..12] of Real;
const
DaysInMonth: array[1..12] of Integer =
(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
MonthNames: array[1..12] of String =
('January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December');
The DaysInMonth constant array is a lookup table — it avoids complex if logic to determine how many days each month has. This is a classic use of arrays as data-driven logic.
Key Functions and Procedures
Determining the Month for a Given Day
A critical helper function: given a day number (1-365), determine which month it falls in.
function DayToMonth(day: Integer): Integer;
var
m, cumulative: Integer;
begin
cumulative := 0;
for m := 1 to 12 do
begin
cumulative := cumulative + DaysInMonth[m];
if day <= cumulative then
begin
DayToMonth := m;
Exit;
end;
end;
DayToMonth := 12; { safety fallback }
end;
This function walks through the cumulative day counts until it finds the month containing the given day. For day 45: January has 31 days (cumulative = 31, 45 > 31), February has 28 (cumulative = 59, 45 <= 59), so day 45 is in February.
Monthly Averages
procedure CalcMonthlyAverages(const temps: TTemperatureArray;
var avgs: TMonthlyAverages);
var
day, month: Integer;
monthTotal: array[1..12] of Real;
monthCount: array[1..12] of Integer;
begin
{ Initialize accumulators }
for month := 1 to 12 do
begin
monthTotal[month] := 0.0;
monthCount[month] := 0;
end;
{ Accumulate temperatures by month }
for day := 1 to DAYS_IN_YEAR do
begin
month := DayToMonth(day);
monthTotal[month] := monthTotal[month] + temps[day];
Inc(monthCount[month]);
end;
{ Calculate averages }
for month := 1 to 12 do
if monthCount[month] > 0 then
avgs[month] := monthTotal[month] / monthCount[month]
else
avgs[month] := 0.0;
end;
This procedure uses the accumulation pattern from Section 9.5, applied to each of 12 months. The monthTotal and monthCount arrays serve as accumulators — a pattern that appears constantly in data analysis.
Finding Hottest and Coldest Days
procedure FindExtremes(const temps: TTemperatureArray;
var hottestDay, coldestDay: Integer);
var
day: Integer;
begin
hottestDay := 1;
coldestDay := 1;
for day := 2 to DAYS_IN_YEAR do
begin
if temps[day] > temps[hottestDay] then
hottestDay := day;
if temps[day] < temps[coldestDay] then
coldestDay := day;
end;
end;
This is the FindMaxIndex / FindMinIndex pattern from Section 9.5, finding both extremes in a single pass through the array.
Detecting Heat Waves
function CountHeatWaves(const temps: TTemperatureArray;
threshold: Real;
minDuration: Integer): Integer;
var
day, consecutive, waves: Integer;
begin
consecutive := 0;
waves := 0;
for day := 1 to DAYS_IN_YEAR do
begin
if temps[day] >= threshold then
Inc(consecutive)
else
begin
if consecutive >= minDuration then
Inc(waves);
consecutive := 0;
end;
end;
{ Check if the year ends during a heat wave }
if consecutive >= minDuration then
Inc(waves);
CountHeatWaves := waves;
end;
This algorithm introduces a new pattern: consecutive counting. We maintain a running count of consecutive days meeting the condition. When the streak breaks, we check if it was long enough to qualify as a heat wave. This pattern appears in many domains — counting consecutive wins, consecutive errors, consecutive trading days above a threshold, and more.
Note the final check after the loop: if the year ends during a heat wave, we must count it. Forgetting this edge case is a common bug.
Temperature Distribution
procedure ShowDistribution(const temps: TTemperatureArray);
var
day, bucket: Integer;
dist: array[0..12] of Integer; { buckets for <0, 0-9, 10-19, ..., 100-109, 110+ }
i: Integer;
begin
for i := 0 to 12 do
dist[i] := 0;
for day := 1 to DAYS_IN_YEAR do
begin
bucket := Trunc(temps[day]) div 10;
if bucket < 0 then bucket := 0;
if bucket > 12 then bucket := 12;
Inc(dist[bucket]);
end;
WriteLn('Temperature Distribution:');
WriteLn(' Below 0: ', dist[0]:4);
for i := 1 to 11 do
WriteLn(' ', (i*10):3, '-', (i*10+9):3, ' F: ', dist[i]:4);
WriteLn(' 110+ F: ', dist[12]:4);
end;
Here we use integer division (div 10) to map each temperature to a decade-based bucket. This bucketing technique transforms continuous data into a discrete histogram — an important pattern in data analysis.
Sample Output
=== Clearwater Regional Weather Report ===
Monthly Averages:
January: 38.2 F
February: 41.5 F
March: 52.7 F
April: 63.1 F
May: 74.8 F
June: 85.3 F
July: 92.1 F
August: 90.6 F
September: 82.4 F
October: 68.9 F
November: 53.2 F
December: 40.8 F
Extreme Temperatures:
Hottest day: Day 198 (July 17) — 108.3 F
Coldest day: Day 23 (January 23) — 12.1 F
Heat Waves (3+ consecutive days >= 95 F):
Found 4 heat waves
Below Freezing: 28 days
Temperature Distribution:
Below 0: 0
10- 19 F: 3
20- 29 F: 12
30- 39 F: 35
40- 49 F: 42
50- 59 F: 38
60- 69 F: 45
70- 79 F: 52
80- 89 F: 58
90- 99 F: 55
100-109 F: 25
110+ F: 0
Lessons Learned
1. Arrays as Lookup Tables
The DaysInMonth and MonthNames constant arrays replace complex conditional logic with simple indexed access. This data-driven approach is cleaner, faster, and less error-prone than chains of if statements.
2. One Array, Many Analyses
A single array of 365 temperatures supports monthly averages, extremes, heat wave detection, freezing counts, and distribution analysis. The data structure is simple; the algorithms provide the intelligence.
3. The Accumulation Pattern is Universal
Monthly totals, distribution buckets, and streak counting are all variations of the same idea: walk through the array, updating accumulators based on each element's value.
4. Edge Cases Matter
The heat wave counter needed a check after the loop to handle streaks that extend to the end of the array. The distribution needed bounds checking for extreme temperatures. Robust code anticipates these boundaries.
5. Helper Functions Simplify Logic
DayToMonth encapsulates a non-trivial mapping, keeping the main analysis procedures clean and focused. Without it, every procedure that needed month information would contain duplicated mapping logic.
Extension Ideas
-
Modify for leap years: Change
DAYS_IN_YEARto 366 and updateDaysInMonth[2]to 29. How little of the code needs to change? (Answer: very little, because we used constants and helper functions.) -
Compare two years: Add a second
TTemperatureArrayand compute the difference in monthly averages between the two years. -
Detect frost dates: Find the last frost in spring (last day before May with temperature <= 32) and the first frost in fall (first day after August with temperature <= 32). These are important agricultural metrics.
-
Add precipitation: Introduce a second parallel array for daily rainfall. Compute monthly precipitation totals. This previews the motivation for records — grouping temperature and precipitation into a single daily record.