Activity 11: Design a Struct for Real Data
Sheet A: weather stations
Odd groups. If your number is even, you want Sheet B.
Group members:
How this works
- Design, ~6 minutes. On this sheet, write:
- a struct for one row of your data, with a type for every field
- a separate struct for the whole collection, holding a
Vecof the first one - three method signatures, each marked
&selfor&mut self. One of them has to change something
- Write one method body, ~3 minutes. Real code if you can, pseudocode if you can't
- Swap sheets, ~4 minutes. Trade with a group that did the other task, and do the three jobs at the bottom of this sheet
A worked example, from a different data set
street species height_m healthy
Comm Ave maple 11.2 yes
Comm Ave oak 14.8 yes
Bay State Rd elm 6.4 no
#![allow(unused)] fn main() { struct Tree { species: String, height_m: f64, healthy: bool } struct Block { street: String, trees: Vec<Tree> } impl Block { /// Height of the tallest tree on the block, or 0.0 if there are none. fn tallest(&self) -> f64 { let mut tallest = 0.0; for tree in &self.trees { if tree.height_m > tallest { tallest = tree.height_m; } } tallest } } }
Your data: five stations report once an hour. Here is the 7am round:
station temp_f wind_mph precip_in sensor_ok
BOS-01 54.3 12.0 0.00 yes
BOS-02 51.9 8.5 0.12 yes
BOS-03 -999.0 0.0 0.00 no
BOS-04 49.7 15.2 0.31 yes
BOS-05 53.1 6.4 0.00 yes
Design a Reading for one row, and a DailyReport that holds a date and all the readings for that day.
Write this method: average_temp, on DailyReport. The average temperature across the day's readings.
Look at BOS-03 before you write it.
Before you design it
Your collection struct holds the date. Pull one row out of it and hand it to someone else. Does that row still know when it happened?
What else would you want on a row, and where would it come from?
Your design
Row struct:
Collection struct:
Three method signatures, each marked &self or &mut self:
The method body you were asked to write:
If you finish early
- What else would be useful on
Readingitself? One example: the temperature in Celsius. Write the signature and a line of pseudocode for the body - Grouping by day is one choice. What else could you group by? You might want one station followed over time, with summary statistics of its own. Sketch that struct: what does it hold, and what methods would it have?
When you get the other group's sheet
Write on their sheet, and sign your group number.
- Check every
&selfand&mut self. Mark any you disagree with and why - Add one other method that could be useful. Get creative! Signature, the right
self, and a line of pseudocode saying what the body does - Name one field whose type you'd change, and say why
Sheet B: shuttle trips
Even groups. If your number is odd, you want Sheet A.
Group members:
How this works
- Design, ~6 minutes. On this sheet, write:
- a struct for one row of your data, with a type for every field
- a separate struct for the whole collection, holding a
Vecof the first one - three method signatures, each marked
&selfor&mut self. One of them has to change something
- Write one method body, ~3 minutes. Real code if you can, pseudocode if you can't
- Swap sheets, ~4 minutes. Trade with a group that did the other task, and do the three jobs at the bottom of this sheet
A worked example, from a different data set
street species height_m healthy
Comm Ave maple 11.2 yes
Comm Ave oak 14.8 yes
Bay State Rd elm 6.4 no
#![allow(unused)] fn main() { struct Tree { species: String, height_m: f64, healthy: bool } struct Block { street: String, trees: Vec<Tree> } impl Block { /// Height of the tallest tree on the block, or 0.0 if there are none. fn tallest(&self) -> f64 { let mut tallest = 0.0; for tree in &self.trees { if tree.height_m > tallest { tallest = tree.height_m; } } tallest } } }
Your data: one row per shuttle arrival. Times are minutes after midnight, so 8:05am is 485:
route stop scheduled_min actual_min riders
CommAve Kenmore Sq 485 489 31
CommAve Silber Way 492 492 44
1BU Danielsen Hall 495 493 12
CommAve Marsh Plaza 500 514 58
1BU Agganis Way 505 507 7
Design a Trip for one row, and a ServiceDay that holds a date and all of the day's trips.
Write this method: late_count, on ServiceDay. How many trips arrived more than threshold minutes after they were scheduled. It takes threshold as a second parameter, after self.
Look at the Danielsen trip before you write it.
Before you design it
Your collection struct holds the date. Pull one row out of it and hand it to someone else. Does that row still know when it happened?
What else would you want on a row, and where would it come from?
Your design
Row struct:
Collection struct:
Three method signatures, each marked &self or &mut self:
The method body you were asked to write:
If you finish early
- What else would be useful on
Tripitself? One example: the scheduled time as hours and minutes, so 485 reads as 8:05. Write the signature and a line of pseudocode for the body - Grouping by day is one choice. What else could you group by? You might want one route followed over time, with summary statistics of its own. Sketch that struct: what does it hold, and what methods would it have?
When you get the other group's sheet
Write on their sheet, and sign your group number.
- Check every
&selfand&mut self. Mark any you disagree with and why - Add one other method that could be useful. Get creative! Signature, the right
self, and a line of pseudocode saying what the body does - Name one field whose type you'd change, and say why
Solutions
Sheet A: weather stations
struct Reading {
station: String,
temp_f: f64,
wind_mph: f64,
precip_in: f64,
sensor_ok: bool,
}
struct DailyReport {
date: String,
readings: Vec<Reading>,
}
Sheet A: average_temp
impl DailyReport {
fn average_temp(&self) -> f64 {
let mut sum = 0.0;
let mut count = 0;
for r in &self.readings {
if !r.sensor_ok { continue; }
sum += r.temp_f;
count += 1;
}
if count == 0 { 0.0 } else { sum / count as f64 }
}
fn add_reading(&mut self, r: Reading) {
self.readings.push(r);
}
}
BOS-03 reports -999.0. Skip it and the average is 52.25. Average it in and you get -158.0.
Sheet B: shuttle trips
One row per shuttle arrival
struct Trip {
route: String,
stop: String,
scheduled_min: u32,
actual_min: u32,
riders: u32,
}
struct ServiceDay {
date: String,
trips: Vec<Trip>,
}
Sheet B: late_count
impl ServiceDay {
fn late_count(&self, threshold: u32) -> u32 {
let mut count = 0;
for t in &self.trips {
if t.actual_min > t.scheduled_min + threshold {
count += 1;
}
}
count
}
fn add_trip(&mut self, t: Trip) {
self.trips.push(t);
}
}
The finish-early prompts. Celsius is fn temp_c(&self) -> f64 { (self.temp_f - 32.0) * 5.0 / 9.0 }. Hours and minutes is fn scheduled_hm(&self) -> (u32, u32) { (self.scheduled_min / 60, self.scheduled_min % 60) }, or a String. Both go on the row struct, not the collection: the method lives on whichever type holds the data it needs.
Before you design it. Neither Reading nor Trip carries a date (only the collection holds it). Both answers are defensible though. A date on every row adds lots copies of the same string, but no date means a row is not a standalone piece of info.
Another way to group it. StationHistory { station: String, readings: Vec<Reading> } with something like max_temp or hours_offline, or RouteHistory { route: String, trips: Vec<Trip> } with on_time_rate. This relates to the last question: when you want to regroup by station instead of by day, every Reading needs to know its own hour and date.