Day 3: Mull It Over
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Rust
Didn’t do anything crazy here – ended up using regex like a bunch of other folks.
solution
use regex::Regex; use crate::shared::util::read_lines; fn parse_mul(input: &[String]) -> (u32, u32) { // Lazy, but rejoin after having removed `\n`ewlines. let joined = input.concat(); let re = Regex::new(r"mul\((\d+,\d+)\)|(do\(\))|(don't\(\))").expect("invalid regex"); // part1 let mut total1 = 0u32; // part2 -- adds `do()`s and `don't()`s let mut total2 = 0u32; let mut enabled = 1u32; re.captures_iter(&joined).for_each(|c| { let (_, [m]) = c.extract(); match m { "do()" => enabled = 1, "don't()" => enabled = 0, _ => { let product: u32 = m.split(",").map(|s| s.parse::<u32>().unwrap()).product(); total1 += product; total2 += product * enabled; } } }); (total1, total2) } pub fn solve() { let input = read_lines("inputs/day03.txt"); let (part1_res, part2_res) = parse_mul(&input); println!("Part 1: {}", part1_res); println!("Part 2: {}", part2_res); } #[cfg(test)] mod test { use super::*; #[test] fn test_solution() { let test_input = vec![ "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))".to_string(), ]; let (p1, p2) = parse_mul(&test_input); eprintln!("P1: {p1}, P2: {p2}"); assert_eq!(161, p1); assert_eq!(48, p2); } }
Solution on my github (Made it public now)
Raku
sub MAIN($input) { grammar Muls { token TOP { .*? <mul>+%.*? .* } token mul { "mul(" <number> "," <number> ")" } token number { \d+ } } my $parsedMuls = Muls.parsefile($input); my @muls = $parsedMuls<mul>.map({.<number>».Int}); my $part-one-solution = @muls.map({[*] $_.List}).sum; say "part 1: $part-one-solution"; grammar EnabledMuls { token TOP { .*? [<.disabled> || <mul>]+%.*? .* } token mul { "mul(" <number> "," <number> ")" } token number { \d+ } token disabled { "don't()" .*? ["do()" || $] } } my $parsedEnabledMuls = EnabledMuls.parsefile($input); my @enabledMuls = $parsedEnabledMuls<mul>.map({.<number>».Int}); my $part-two-solution = @enabledMuls.map({[*] $_.List}).sum; say "part 2: $part-two-solution"; }
Smalltalk
I wrote
matchesActual
cause all of smalltalk’sstupidmatchesDo:
or whatever don’t give you the actual match with captures, only substrings (that wasted a good 40 minutes).Also smalltalk really needs an index operator
day3p1: input | reg sum | reg := 'mul\((\d\d?\d?),(\d\d?\d?)\)' asRegex. sum := 0. reg matchesActual: input do: [ :m | " + sum at end cause operator precedence" sum := (m subexpression: 2) asInteger * (m subexpression: 3) asInteger + sum ]. ^ sum.
day3p2: input | reg sum do val | reg := 'do(\:?n''t)?\(\)|mul\((\d{1,3}),(\d{1,3})\)' asRegex. sum := 0. do := true. reg matchesActual: input do: [ :m | val := m subexpression: 1. (val at: 1) = $d ifTrue: [ do := (val size < 5) ] ifFalse: [ do ifTrue: [ sum := (m subexpression: 2) asInteger * (m subexpression: 3) asInteger + sum. ]. ]. ]. ^ sum.
I couldn’t figure it out in haskell, so I went with bash for the first part
Shell
cat example | grep -Eo "mul\([[:digit:]]{1,3},[[:digit:]]{1,3}\)" | cut -d "(" -f 2 | tr -d ")" | tr "," "*" | paste -sd+ | bc
but this wouldn’t rock anymore in the second part, so I had to resort to python for it
Python
import sys f = "\n".join(sys.stdin.readlines()) f = f.replace("don't()", "\ndon't()\n") f = f.replace("do()", "\ndo()\n") import re enabled = True muls = [] for line in f.split("\n"): if line == "don't()": enabled = False if line == "do()": enabled = True if enabled: for match in re.finditer(r"mul\((\d{1,3}),(\d{1,3})\)", line): muls.append(int(match.group(1)) * int(match.group(2))) pass pass print(sum(muls))
Nice, sometimes a few extra linebreaks can do the trick…
Really cool trick. I did a bunch of regex matching that I’m sure I won’t remember how it works few weeks from now, this is so much readable
My first insinct was similar, add line breaks to the do and dont modifiers. But I got toa caught up thinking id have to keep track of the added characters, I wound up just abusing split()-
Kotlin
fun part1(input: String): Int { val pattern = "mul\\((\\d{1,3}),(\\d{1,3})\\)".toRegex() var sum = 0 pattern.findAll(input).forEach { match -> val first = match.groups[1]?.value?.toInt()!! val second = match.groups[2]?.value?.toInt()!! sum += first * second } return sum } fun part2(input: String): Int { val pattern = "mul\\((\\d{1,3}),(\\d{1,3})\\)|don't\\(\\)|do\\(\\)".toRegex() var sum = 0 var enabled = true pattern.findAll(input).forEach { match -> if (match.value == "do()") enabled = true else if (match.value == "don't()") enabled = false else if (enabled) { val first = match.groups[1]?.value?.toInt()!! val second = match.groups[2]?.value?.toInt()!! sum += first * second } } return sum }
You can avoid having to escape the backslashes in regexps by using multiline strings:
val pattern = """mul\((\d{1,3}),(\d{1,3})\)""".toRegex()
I started poking at doing a proper lexer/parser, but then I thought about how early in AoC it is and how low the chance is that the second part will require proper parsing.
So therefore; hello regex my old friend, I’ve come to talk with you again.
C#
List<string> instructions = new List<string>(); public void Input(IEnumerable<string> lines) { foreach (var line in lines) instructions.AddRange(Regex.Matches(line, @"mul\(\d+,\d+\)|do\(\)|don't\(\)").Select(m => m.Value)); } public void Part1() { var sum = instructions.Select(mul => Regex.Match(mul, @"(\d+),(\d+)").Groups.Values.Skip(1).Select(g => int.Parse(g.Value))).Select(cc => cc.Aggregate(1, (acc, val) => acc * val)).Sum(); Console.WriteLine($"Sum: {sum}"); } public void Part2() { bool enabled = true; long sum = 0; foreach(var inst in instructions) { if (inst.StartsWith("don't")) enabled = false; else if (inst.StartsWith("do")) enabled = true; else if (enabled) sum += Regex.Match(inst, @"(\d+),(\d+)").Groups.Values.Skip(1).Select(g => int.Parse(g.Value)).Aggregate(1, (acc, val) => acc * val); } Console.WriteLine($"Sum: {sum}"); }
Haskell
Oof, a parsing problem :/ This is some nasty-ass code.
step
is almost the State monad written out explicitly.Solution
import Control.Monad import Data.Either import Data.List import Text.Parsec data Ins = Mul !Int !Int | Do | Dont readInput :: String -> [Ins] readInput = fromRight undefined . parse input "" where input = many ins <* many anyChar ins = choice . map try $ [ Mul <$> (string "mul(" *> arg) <*> (char ',' *> arg) <* char ')', Do <$ string "do()", Dont <$ string "don't()", anyChar *> ins ] arg = do s <- many1 digit guard $ length s <= 3 return $ read s run f = snd . foldl' step (True, 0) where step (e, a) i = case i of Mul x y -> (e, if f e then a + x * y else a) Do -> (True, a) Dont -> (False, a) main = do input <- readInput <$> readFile "input03" print $ run (const True) input print $ run id input
Love to see you chewing through this parsing problem in Haskell, I didn’t dare use Parsec because I wasn’t confident enough.
Why did you decide to have a strict definition ofMul !Int !Int
?My guess is because a linter and/or HLS was suggesting it. I know HLS used to suggest making your fields strict in almost all cases. In this case I have a hunch that it slightly cuts down on memory usage because we use almost all
Mul
s either way. So it does not need to keep the string it is parsed from in memory as part of the thunk.But it probably makes a small/negligible difference here.
Yep, HLS suggested it, and I figured since I’m definitely going to be using all of the values (in part one, at least), why not?
Normally I ignore that kind of nitpicky suggestion though.
Python
After a bunch of fiddling yesterday and today I finally managed to arrive at a regex-only solution for part 2. That
re.DOTALL
is crucial here.import re from pathlib import Path def parse_input_one(input: str) -> list[tuple[int]]: p = re.compile(r"mul\((\d{1,3}),(\d{1,3})\)") return [(int(m[0]), int(m[1])) for m in p.findall(input)] def parse_input_two(input: str) -> list[tuple[int]]: p = re.compile(r"don't\(\).*?do\(\)|mul\((\d{1,3}),(\d{1,3})\)", re.DOTALL) return [(int(m[0]), int(m[1])) for m in p.findall(input) if m[0] and m[1]] def part_one(input: str) -> int: pairs = parse_input_one(input) return sum(map(lambda v: v[0] * v[1], pairs)) def part_two(input: str) -> int: pairs = parse_input_two(input) return sum(map(lambda v: v[0] * v[1], pairs)) if __name__ == "__main__": input = Path("input").read_text("utf-8") print(part_one(input)) print(part_two(input))
Gleam
Struggled with the second part as I am still very new to this very cool language, but got there after scrolling for some inspiration.
import gleam/int import gleam/io import gleam/list import gleam/regex import gleam/result import gleam/string import simplifile pub fn main() { let assert Ok(data) = simplifile.read("input.in") part_one(data) |> io.debug part_two(data) |> io.debug } fn part_one(data) { let assert Ok(multiplication_pattern) = regex.from_string("mul\\(\\d{1,3},\\d{1,3}\\)") let assert Ok(digit_pattern) = regex.from_string("\\d{1,3},\\d{1,3}") let multiplications = regex.scan(multiplication_pattern, data) |> list.flat_map(fn(reg) { regex.scan(digit_pattern, reg.content) |> list.map(fn(digits) { digits.content |> string.split(",") |> list.map(fn(x) { x |> int.parse |> result.unwrap(0) }) |> list.reduce(fn(a, b) { a * b }) |> result.unwrap(0) }) }) |> list.reduce(fn(a, b) { a + b }) |> result.unwrap(0) } fn part_two(data) { let data = "do()" <> string.replace(data, "\n", "") <> "don't()" let assert Ok(pattern) = regex.from_string("do\\(\\).*?don't\\(\\)") regex.scan(pattern, data) |> list.map(fn(input) { input.content |> part_one }) |> list.reduce(fn(a, b) { a + b }) }
Nim
From a first glance it was obviously a regex problem.
I’m using tinyre here instead of stdlibre
library just because I’m more familiar with it.import pkg/tinyre proc solve(input: string): AOCSolution[int, int] = var allow = true for match in input.match(reG"mul\(\d+,\d+\)|do\(\)|don't\(\)"): if match == "do()": allow = true elif match == "don't()": allow = false else: let pair = match[4..^2].split(',') let mult = pair[0].parseInt * pair[1].parseInt result.part1 += mult if allow: result.part2 += mult
I shy away from regexes for these parsing problems because part 2 likes to mess those up but here it worked beautifully. Nice and compact solution!
Haskell
module Main where import Control.Arrow hiding ((+++)) import Data.Char import Data.Functor import Data.Maybe import Text.ParserCombinators.ReadP hiding (get) import Text.ParserCombinators.ReadP qualified as P data Op = Mul Int Int | Do | Dont deriving (Show) parser1 :: ReadP [(Int, Int)] parser1 = catMaybes <$> many ((Just <$> mul) <++ (P.get $> Nothing)) parser2 :: ReadP [Op] parser2 = catMaybes <$> many ((Just <$> operation) <++ (P.get $> Nothing)) mul :: ReadP (Int, Int) mul = (,) <$> (string "mul(" *> (read <$> munch1 isDigit <* char ',')) <*> (read <$> munch1 isDigit <* char ')') operation :: ReadP Op operation = (string "do()" $> Do) +++ (string "don't()" $> Dont) +++ (uncurry Mul <$> mul) foldOp :: (Bool, Int) -> Op -> (Bool, Int) foldOp (_, n) Do = (True, n) foldOp (_, n) Dont = (False, n) foldOp (True, n) (Mul a b) = (True, n + a * b) foldOp (False, n) _ = (False, n) part1 = sum . fmap (uncurry (*)) . fst . last . readP_to_S parser1 part2 = snd . foldl foldOp (True, 0) . fst . last . readP_to_S parser2 main = getContents >>= print . (part1 &&& part2)
Of course it’s point-free
TypeScript
Solution
import { AdventOfCodeSolutionFunction } from "./solutions"; export const solution_3: AdventOfCodeSolutionFunction = (input) => { const mul_regex = /mul\((\d+),(\d+)\)/g; // mul() const do_regex = /do\(\)/g; // do() const do_not_regex = /don\'t\(\)/g; // don't() const doLength = "do()".length; const doNotLength = "don't()".length; let input_copy = "" + input; let part_1 = 0; let part_2 = 0; let isEnabled = true; while (true) { const nextMul = input_copy.search(mul_regex); const nextDo = input_copy.search(do_regex); const nextDoNot = input_copy.search(do_not_regex); let pointer = Number.POSITIVE_INFINITY; // find the smallest while ignoring items that are not found if (nextMul != -1) pointer = Math.min(pointer, nextMul); if (nextDo != -1) pointer = Math.min(pointer, nextDo); if (nextDoNot != -1) pointer = Math.min(pointer, nextDoNot); // no matches if (pointer == Number.POSITIVE_INFINITY) break // handle found command switch (pointer) { case nextDo: { pointer += doLength; isEnabled = true; break; } case nextDoNot: { pointer += doNotLength; isEnabled = false; break; } case nextMul: { const res = input_copy.matchAll(mul_regex).next().value; if (!res) { // this should never happen but here's an escape hatch throw Error("regex result is undefined or null"); } // add the length of the whole capture to the pointer pointer += res[0].length; // multiply capture groups const comp = Number(res[1]) * Number(res[2]); // part 1 sum part_1 += comp; // part 2 sum if(isEnabled) part_2 += comp; break; } } // shorten the start of the string input_copy = input_copy.slice(pointer); } return { part_1, part_2, }; }
This one was harder but still. I feel like I can improve it for sure :)
Elixir
First time writing Elixir. It’s probably janky af.
I’ve had some help from AI to get some pointers along the way. I’m not competing in any way, just trying to learn and have fun.
~~Part 2 is currently not working, and I can’t figure out why. I’m trying to just remove everything from “don’t()” to “do()” and just pass the rest through the working solution for part 1. Should work, right?
Any pointers?~~
edit; working solution:
defmodule Three do def get_input do File.read!("./input.txt") end def extract_operations(input) do Regex.scan(~r/mul\((\d{1,3}),(\d{1,3})\)/, input) |> Enum.map(fn [_op, num1, num2] -> num1 = String.to_integer(num1) num2 = String.to_integer(num2) [num1 * num2] end) end def sum_products(ops) do List.flatten(ops) |> Enum.filter(fn x -> is_integer(x) end) |> Enum.sum() end def part1 do extract_operations(get_input()) |> sum_products() end def part2 do String.split(get_input(), ~r/don\'t\(\)[\s\S]*?do\(\)/) |> Enum.map(&extract_operations/1) |> sum_products() end end IO.puts("part 1: #{Three.part1()}") IO.puts("part 2: #{Three.part2()}")
Part 2 is currently not working, and I can’t figure out why. I’m trying to just remove everything from “don’t()” to “do()” and just pass the rest through the working solution for part 1. Should work, right?
I think I had the same issue. Consider what happens if there isn’t a do() after a don’t().
Ah, yes, that’s it. The lazy solution would be to add a “do()” to the end of the input, right? Haha
It was actually a line break that broke the regex. Changing from a “.” to “[\s\S]” fixed it.
J
We can take advantage of the manageable size of the input to avoid explicit looping and mutable state; instead, construct vectors which give, for each character position in the input, the position of the most recent
do()
and most recentdon't()
; for part 2 a multiplication is enabled if the position of the most recentdo()
(counting start of input as 0) is greater than that of the most recentdon't()
(counting start of input as minus infinity).load 'regex' raw =: fread '3.data' mul_matches =: 'mul\(([[:digit:]]{1,3}),([[:digit:]]{1,3})\)' rxmatches raw NB. a b sublist y gives elements [a..b) of y sublist =: ({~(+i.)/)~"1 _ NB. ". is number parsing mul_results =: */"1 ". (}."2 mul_matches) sublist raw result1 =: +/ mul_results do_matches =: 'do\(\)' rxmatches raw dont_matches =: 'don''t\(\)' rxmatches raw match_indices =: (<0 0) & {"2 do_indices =: 0 , match_indices do_matches NB. start in do mode dont_indices =: match_indices dont_matches NB. take successive diffs, then append length from last index to end of string run_lengths =: (}. - }:) , (((#raw) & -) @: {:) do_map =: (run_lengths do_indices) # do_indices dont_map =: (({. , run_lengths) dont_indices) # __ , dont_indices enabled =: do_map > dont_map result2 =: +/ ((match_indices mul_matches) { enabled) * mul_results
Rust feat. pest
No Zalgo here! I wasted a huge amount of time by not noticing that the second part’s example input was different - my code worked fine but my test failed 🤦♂️
pest.rs is lovely, although part two made my PEG a bit ugly.
part1 = { SOI ~ (mul_expr | junk)+ ~ EOI } part2 = { (enabled | disabled)+ ~ EOI } mul_expr = { "mul(" ~ number ~ "," ~ number ~ ")" } number = { ASCII_DIGIT{1,3} } junk = _{ ASCII } on = _{ "do()" } off = _{ "don't()" } enabled = _{ (SOI | on) ~ (!(off) ~ (mul_expr | junk))+ } disabled = _{ off ~ (!(on) ~ junk)+ }
use std::fs; use color_eyre::eyre; use pest::Parser; use pest_derive::Parser; #[derive(Parser)] #[grammar = "memory.pest"] pub struct MemoryParser; fn parse(input: &str, rule: Rule) -> eyre::Result<usize> { let sum = MemoryParser::parse(rule, input)? .next() .expect("input must be ASCII") .into_inner() .filter(|pair| pair.as_rule() == Rule::mul_expr) .map(|pair| { pair.into_inner() .map(|num| num.as_str().parse::<usize>().unwrap()) .product::<usize>() }) .sum(); Ok(sum) } fn part1(filepath: &str) -> eyre::Result<usize> { let input = fs::read_to_string(filepath)?; parse(&input, Rule::part1) } fn part2(filepath: &str) -> eyre::Result<usize> { let input = fs::read_to_string(filepath)?; parse(&input, Rule::part2) } fn main() -> eyre::Result<()> { color_eyre::install()?; let part1 = part1("d03/input.txt")?; let part2 = part2("d03/input.txt")?; println!("Part 1: {part1}\nPart 2: {part2}"); Ok(()) }