-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.rs
184 lines (151 loc) · 4.73 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use std::env;
use std::fs::File;
use std::io::Error;
use std::io::*;
use std::io::ErrorKind::{InvalidData, UnexpectedEof};
// General function to parse whitespace-separated numbers
fn parse_numbers(line: &str) -> Result<Vec<i32>> {
let mut numbers = Vec::new();
let mut num = String::new();
for c in line.chars() {
if c.is_numeric() {
num.push(c);
} else if c.is_whitespace() {
if num.len() > 0 {
numbers.push(match num.parse::<i32>() {
Err(why) => return Err(Error::new(InvalidData, why.to_string())),
Ok(num) => num,
});
num.clear();
}
} else {
return Err(Error::new(InvalidData, format!("Invalid format! Unexpected character '{}'", c)));
}
}
if num.len() > 0 {
numbers.push(match num.parse::<i32>() {
Err(why) => return Err(Error::new(InvalidData, why.to_string())),
Ok(num) => num,
});
}
Ok(numbers)
}
struct Card {
num: i32,
winning: Vec<i32>,
present: Vec<i32>,
matches: usize,
}
impl Card {
fn from_string(line: &str) -> Result<Card> {
// Separate 'Card #:' from the start of the string
let (_, numbers) = match line.split_once(':') {
None => return Err(Error::new(UnexpectedEof, "Unexpected end-of-line!")),
Some((prefix, suffix)) => (prefix, suffix),
};
// Separate '# # #... | # # # ...'
let (prefix, suffix) = match numbers.split_once('|') {
None => return Err(Error::new(InvalidData, "Invalid format! Missing '|'")),
Some((first, last)) => (first, last),
};
// Allocate vectors for the sets of numbers
let winning = parse_numbers(prefix)?;
let present = parse_numbers(suffix)?;
// Precompute the number of matches (save alot of time later)
let mut matches = 0;
for x in present {
if winning.contains(&x) {
matches += 1;
}
}
// Parse numbers and create struct
// winning numbers from the start and present numbers from the end of the string
Ok(Card {
num: -1,
winning: parse_numbers(prefix)?,
present: parse_numbers(suffix)?,
matches: matches,
})
}
fn score(&self) -> u32 {
// This function was written before I pre-computed the matches
let mut total = 0;
for i in self.present.iter() {
if self.winning.contains(&i) {
if total == 0 {
total += 1
} else {
total *= 2
}
}
}
return total;
}
}
fn open_reader(path: Option<&str>) -> Result<impl Iterator<Item = Result<String>>> {
let source: Box<dyn Read> = match path {
None => Box::new(stdin()),
Some(p) => Box::new(File::open(p)?),
};
Ok(BufReader::new(source).lines())
}
fn parse_cards(source: impl Iterator<Item = Result<String>>) -> Result<Vec<Card>> {
let mut cards = Vec::new();
let mut i = 1; // Counter for the card number
for line in source {
let mut next = Card::from_string(&line?)?;
next.num = i; i += 1;
cards.push(next);
}
return Ok(cards)
}
// PART 1
fn calculate_total_score(cards: &Vec<Card>) -> u32 {
let mut total = 0;
for card in cards {
total += card.score();
}
return total;
}
// PART 2
fn calculate_card_copies(cards: &Vec<Card>) -> Result<u64> {
/* Implements the following rule for Part 2
There's no such thing as "points". Instead, scratchcards only cause you to win more scratchcards equal to the number of winning numbers you have.
Specifically, you win copies of the scratchcards below the winning card equal to the number of matches.
So, if card 10 were to have 5 matching numbers, you would win one copy each of cards 11, 12, 13, 14, and 15.
Copies of scratchcards are scored like normal scratchcards and have the same card number as the card they copied.
So, if you win a copy of card 10 and it has 5 matching numbers, it would then win a copy of the same cards that the
original card 10 won: cards 11, 12, 13, 14, and 15. This process repeats until none of the copies cause you to win any more cards.
(Cards will never make you copy a card past the end of the table.)
*/
// Use a stack structure to store the indexes of copies
let mut copies = Vec::with_capacity(cards.len());
for _ in 0..cards.len() { copies.push(1); } // Initalize vector with 1s
// Outer loop to iterate once over all the cards
for i in 0..cards.len() {
let matches = cards[i].matches;
if i + matches > cards.len() {
return Err(Error::new(InvalidData, "References cards that don't exist!"));
}
// Add 1 copy to every x following index for each copy in the current
for x in 0..matches {
copies[i + x + 1] += copies[i];
}
}
let mut total = 0;
for x in copies {
total += x;
}
Ok(total)
}
fn main() {
let args:Vec<String> = env::args().collect();
let path = if args.len() > 1 {
Some(args[1].as_str())
} else {
None
};
let cards = parse_cards(open_reader(path).unwrap()).unwrap();
println!("Part 1: {}", calculate_total_score(&cards));
println!("Part 2: {}", calculate_card_copies(&cards).unwrap());
}