|
@@ -6,6 +6,7 @@ use std::fs::File;
|
6
|
6
|
use std::io::{BufRead, BufReader};
|
7
|
7
|
use std::fmt;
|
8
|
8
|
use std::collections::{HashMap, HashSet};
|
|
9
|
+use std::collections::hash_map::Entry;
|
9
|
10
|
use std::iter::FromIterator;
|
10
|
11
|
|
11
|
12
|
use chrono::prelude::*;
|
|
@@ -27,6 +28,16 @@ enum Record {
|
27
|
28
|
},
|
28
|
29
|
}
|
29
|
30
|
|
|
31
|
+impl Record {
|
|
32
|
+ fn time(&self) -> NaiveDateTime {
|
|
33
|
+ match *self {
|
|
34
|
+ Record::Start { time, guard_id: _ } => time,
|
|
35
|
+ Record::Sleep { time } => time,
|
|
36
|
+ Record::Wake { time } => time,
|
|
37
|
+ }
|
|
38
|
+ }
|
|
39
|
+}
|
|
40
|
+
|
30
|
41
|
#[derive(Debug, Clone, PartialEq)]
|
31
|
42
|
struct MalformedRecord {
|
32
|
43
|
details: String
|
|
@@ -50,35 +61,78 @@ impl Error for MalformedRecord {
|
50
|
61
|
}
|
51
|
62
|
}
|
52
|
63
|
|
|
64
|
+pub fn solve_part1() -> Result<u32, Box<Error>> {
|
|
65
|
+ Ok(get_part1(INPUT)?)
|
|
66
|
+}
|
|
67
|
+
|
|
68
|
+fn get_part1(filename: &str) -> Result<u32, Box<Error>> {
|
|
69
|
+ let records = read_records(filename)?;
|
|
70
|
+ let minutes_asleep = minutes_asleep_per_guard(records);
|
|
71
|
+ let sleepiest_guard = minutes_asleep.iter().max_by_key(|&(_, mins)| mins.len()).unwrap();
|
|
72
|
+ let sleepiest_minute = mode(sleepiest_guard.1);
|
|
73
|
+ Ok(sleepiest_guard.0 * sleepiest_minute)
|
|
74
|
+}
|
|
75
|
+
|
|
76
|
+fn mode(numbers: &[u32]) -> u32 {
|
|
77
|
+ let mut occurences = HashMap::new();
|
|
78
|
+ for &value in numbers {
|
|
79
|
+ *occurences.entry(value).or_insert(0) += 1;
|
|
80
|
+ }
|
|
81
|
+ occurences
|
|
82
|
+ .into_iter()
|
|
83
|
+ .max_by_key(|&(_, count)| count)
|
|
84
|
+ .map(|(val, _)| val)
|
|
85
|
+ .unwrap_or(0)
|
|
86
|
+}
|
|
87
|
+
|
|
88
|
+fn minutes_asleep_per_guard(mut records: Vec<Record>) -> HashMap<u32, Vec<u32>> {
|
|
89
|
+ let mut minutes_asleep: HashMap<u32, Vec<u32>> = HashMap::new();
|
|
90
|
+ records.sort_by_key(|r| r.time());
|
|
91
|
+ let mut current_guard = 0;
|
|
92
|
+ let mut fell_asleep = 0;
|
|
93
|
+ for record in records {
|
|
94
|
+ match record {
|
|
95
|
+ Record::Start { time: _, guard_id } => current_guard = guard_id,
|
|
96
|
+ Record::Sleep { time } => fell_asleep = time.minute(),
|
|
97
|
+ Record::Wake { time } => {
|
|
98
|
+ let mut slept_minutes = (fell_asleep..time.minute()).collect();
|
|
99
|
+ match minutes_asleep.entry(current_guard) {
|
|
100
|
+ Entry::Vacant(e) => { e.insert(slept_minutes); },
|
|
101
|
+ Entry::Occupied(mut e) => { e.get_mut().append(&mut slept_minutes); },
|
|
102
|
+ }
|
|
103
|
+ }
|
|
104
|
+ }
|
|
105
|
+ }
|
|
106
|
+ minutes_asleep
|
|
107
|
+}
|
|
108
|
+
|
53
|
109
|
fn read_records(filename: &str) -> Result<Vec<Record>, Box<Error>> {
|
54
|
110
|
let mut records: Vec<Record> = Vec::new();
|
55
|
111
|
let record_regex =
|
56
|
|
- Regex::new(r"\[(?P<timestamp>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2})\]\s(?:(?P<start>Guard #(?P<guard_id>\d+) begins shift)|(?P<sleep>falls asleep)|(?P<wake>wakes up))")?;
|
|
112
|
+ Regex::new(concat!(
|
|
113
|
+ r"\[(?P<timestamp>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2})\]\s(?:",
|
|
114
|
+ r"(?P<start>Guard #(?P<guard_id>\d+) begins shift)|",
|
|
115
|
+ r"(?P<sleep>falls asleep)|",
|
|
116
|
+ r"(?P<wake>wakes up))"))?;
|
57
|
117
|
let file = File::open(filename)?;
|
58
|
118
|
for line in BufReader::new(file).lines() {
|
59
|
119
|
match record_regex.captures(&line?) {
|
60
|
120
|
Some(captures) => {
|
61
|
|
- println!("{:?}", NaiveDateTime::parse_from_str(
|
|
121
|
+ let time = NaiveDateTime::parse_from_str(
|
62
|
122
|
&get_captured_field(&captures, "timestamp")?,
|
63
|
|
- "%Y-%m-%d %H:%M")?);
|
|
123
|
+ "%Y-%m-%d %H:%M")?;
|
64
|
124
|
if has_captured_field(&captures, "start")? {
|
65
|
125
|
records.push(Record::Start {
|
66
|
|
- time: NaiveDateTime::parse_from_str(
|
67
|
|
- &get_captured_field(&captures, "timestamp")?,
|
68
|
|
- "%Y-%m-%d %H:%M")?,
|
|
126
|
+ time: time,
|
69
|
127
|
guard_id: get_captured_field(&captures, "guard_id")?.parse()?,
|
70
|
128
|
});
|
71
|
129
|
} else if has_captured_field(&captures, "sleep")? {
|
72
|
130
|
records.push(Record::Sleep {
|
73
|
|
- time: NaiveDateTime::parse_from_str(
|
74
|
|
- &get_captured_field(&captures, "timestamp")?,
|
75
|
|
- "%Y-%m-%d %H:%M")?,
|
|
131
|
+ time: time,
|
76
|
132
|
});
|
77
|
133
|
} else {
|
78
|
134
|
records.push(Record::Wake {
|
79
|
|
- time: NaiveDateTime::parse_from_str(
|
80
|
|
- &get_captured_field(&captures, "timestamp")?,
|
81
|
|
- "%Y-%m-%d %H:%M")?,
|
|
135
|
+ time: time,
|
82
|
136
|
});
|
83
|
137
|
}
|
84
|
138
|
},
|
|
@@ -203,4 +257,30 @@ mod tests {
|
203
|
257
|
),
|
204
|
258
|
}
|
205
|
259
|
}
|
|
260
|
+
|
|
261
|
+ #[test]
|
|
262
|
+ fn gets_minutes_asleep_per_guard() {
|
|
263
|
+ let mut expected: HashMap<u32, Vec<u32>> = HashMap::new();
|
|
264
|
+ expected.insert(10, vec![5, 6, 7, 8, 9]);
|
|
265
|
+ assert_eq!(minutes_asleep_per_guard(vec![
|
|
266
|
+ Record::Sleep {
|
|
267
|
+ time: NaiveDateTime::parse_from_str(
|
|
268
|
+ "1518-11-01 00:05", "%Y-%m-%d %H:%M").unwrap(),
|
|
269
|
+ },
|
|
270
|
+ Record::Start {
|
|
271
|
+ time: NaiveDateTime::parse_from_str(
|
|
272
|
+ "1518-11-01 00:00", "%Y-%m-%d %H:%M").unwrap(),
|
|
273
|
+ guard_id: 10,
|
|
274
|
+ },
|
|
275
|
+ Record::Wake {
|
|
276
|
+ time: NaiveDateTime::parse_from_str(
|
|
277
|
+ "1518-11-01 00:10", "%Y-%m-%d %H:%M").unwrap(),
|
|
278
|
+ },
|
|
279
|
+ ]), expected);
|
|
280
|
+ }
|
|
281
|
+
|
|
282
|
+ #[test]
|
|
283
|
+ fn solves_part1() {
|
|
284
|
+ assert_eq!(get_part1(TEST_INPUT).unwrap(), 240);
|
|
285
|
+ }
|
206
|
286
|
}
|