Day 3 - Regular expressions
Created on Tuesday, December 3, 2024.
Ok, so today looks like it's mostly a parsing challange.
I've stretched myself the last few with trying to do things in a functional way, and while I probably could build some kind of iterator here, I think I'm just going to go for the simple solution which is to use a regular expression
I say simple, I've not done this in rust before.
The regular expression is fairly easy, mul\((\d+),(\d+)\)
is what we're looking for. With grouping rules, that should consume the word mul and the open parenthesis, and then return the first digit, consume the comma, return the second digit and then consume the closing parenthesis
It looks like Rust doesn't have native regex support, so we'll crate in the regex library, which looks like the standard one using cargo add regex.
Regex looks like it operates by creating a new regex string, and then we can use the captures_iter to iterate through the captures using the extract method bring them out into a match object and the groups. The example has a handy example of naming parts of the group, so for (_, [group1, group2]) in re.captures_iter(line).map(|c|c.extract()) {}
should do the trick
pub fn input_generator(input: &str) -> GeneratorResult {
re.captures_iter(input)
.map(|c| c.extract())
.map(|(_, [a, b])| {
(
a.parse::<u32>().expect("Failed to parse number"),
b.parse::<u32>().expect("Failed to parse number"),
)
})
.collect()
}
Some funky parrsing in this case, but the actual answer code is trivial...
input.iter().map(|(a, b)| a * b).sum()
Onto part 2...
Part 2 - Dont stop doing
Now we need to handle commands to enable and disable functions!
Oh, ok, this is more fun. Now we need to parse out a series of instructions, the enable and the disable instruction.
Having learned from previous years, careful reading indicates that this isn't like a stacking system, two don'ts followed by a do still enables mul instructions.
There's a number of ways we could do this, but I'm not sure my regex fu is good enough to handle some of them.
We could use the regex to essentially delete all characters between the Dont and the next do, but I suspect that would have some hostile input, like dodoxxxdontnt, which might cause issues.
Alternately, we can parse for either mul, do, and don't tokens, and then run along the stream of tokens, multiplying and adding only when the state is enabled. We'd use a fold left with a tuple (or struct), starting something like (0, true), and if we see don't, we'd return (sum, false). if we see do, we return (sum, true) and if we see mul, if the second flag is true, we add the sum to the accumulator.
The hard part here for me is the regex extracting those commands, I might generate a triple instead of just the pair, with the first argument indicating the command type
Lets give that a go
Regex::new(r"(mul|do|don't)\((\d*),?(\d*)\)").expect("Regex compilation shouldn't fail!");
re.captures_iter(input)
.map(|c| c.extract())
.map(|(_, [cmd, a, b])| match cmd {
"do" => Command::ENABLE,
"don't" => Command::DISABLE,
"mul" => Command::MUL(
a.parse::<u32>().expect("Failed to parse number"),
b.parse::<u32>().expect("Failed to parse number"),
),
_ => panic!("This shouldn't happen"),
})
.collect()
Now we're using an Enum called Command to indicate which command we have, this returns a nice list of commands, which we can iterate over and apply as we sum things together, again because we're passing state around, we're going to use the fold function.
input
.iter()
.fold((0, true), |(sum, active), cmd| match cmd {
Command::ENABLE => (sum, true),
Command::DISABLE => (sum, false),
Command::MUL(a, b) => {
if active {
(sum + (a * b), true)
} else {
(sum, active)
}
}
})
.0
And done!
Next
Previous