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
Sorry for the delay posting this one, Ategon seemed to have it covered, so I forgot :D I will do better.
Haskell
Oof, a parsing problem :/ This is some nasty-ass code.
stepis 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 inputLove 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
Muls 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.
Nim
From a first glance it was obviously a regex problem.
I’m using tinyre here instead of stdlibrelibrary 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!
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
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
Uiua
Uses experimental feature of
foldto track the running state of do/don’t.[edit] Slightly re-written to make it less painful :-) Try it online!
# Experimental! DataP₁ ← $ xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5)) DataP₂ ← $ xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)) GetMul ← $ mul\((\d{1,3}),(\d{1,3})\) GetMulDoDont ← $ mul\(\d{1,3},\d{1,3}\)|do\(\)|don\'t\(\) &p/+≡(/×≡⋕↘1)regexGetMul DataP₁ # Part 1 # Build an accumulator to track running state of do/don't Filter ← ↘1⊂:∧(⍣(0 °"don"|1 °"do("|.◌)) :1≡(↙3°□) ≡⊢ regex GetMulDoDont DataP₂ ▽⊸≡◇(≍"mul"↙3)▽⊸Filter # Apply Filter, remove the spare 'do's &p/+≡◇(/×≡◇⋕↘1⊢regexGetMul) # Get the digits and multiply, sum.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.
Python
Part1:
matches = re.findall(r"(mul\((\d+),(\d+)\))", input) muls = [int(m[1]) * int(m[2]) for m in matches] print(sum(muls))Part2:
instructions = list(re.findall(r"(do\(\)|don't\(\)|(mul\((\d+),(\d+)\)))", input) mul_enabled = True muls = 0 for inst in instructions: if inst[0] == "don't()": mul_enabled = False elif inst[0] == "do()": mul_enabled = True elif mul_enabled: muls += int(inst[2]) * int(inst[3]) print(muls)Go
Part 1, just find the regex groups, parse to int, and done.
Part 1
func part1() { file, _ := os.Open("input.txt") defer file.Close() scanner := bufio.NewScanner(file) re := regexp.MustCompile(`mul\(([0-9]{1,3}),([0-9]{1,3})\)`) product := 0 for scanner.Scan() { line := scanner.Text() submatches := re.FindAllStringSubmatch(line, -1) for _, s := range submatches { a, _ := strconv.Atoi(s[1]) b, _ := strconv.Atoi(s[2]) product += (a * b) } } fmt.Println(product) }Part 2, not so simple. Ended up doing some weird hack with a map to check if the multiplication was enabled or not. Also instead of finding regex groups I had to find the indices, and then interpret what those mean… Not very readable code I’m afraid
Part2
func part2() { file, _ := os.Open("input.txt") defer file.Close() scanner := bufio.NewScanner(file) mulRE := regexp.MustCompile(`mul\(([0-9]{1,3}),([0-9]{1,3})\)`) doRE := regexp.MustCompile(`do\(\)`) dontRE := regexp.MustCompile(`don't\(\)`) product := 0 enabled := true for scanner.Scan() { line := scanner.Text() doIndices := doRE.FindAllStringIndex(line, -1) dontIndices := dontRE.FindAllStringIndex(line, -1) mulSubIndices := mulRE.FindAllStringSubmatchIndex(line, -1) mapIndices := make(map[int]string) for _, do := range doIndices { mapIndices[do[0]] = "do" } for _, dont := range dontIndices { mapIndices[dont[0]] = "dont" } for _, mul := range mulSubIndices { mapIndices[mul[0]] = "mul" } nextMatch := 0 for i := 0; i < len(line); i++ { val, ok := mapIndices[i] if ok && val == "do" { enabled = true } else if ok && val == "dont" { enabled = false } else if ok && val == "mul" { if enabled { match := mulSubIndices[nextMatch] a, _ := strconv.Atoi(string(line[match[2]:match[3]])) b, _ := strconv.Atoi(string(line[match[4]:match[5]])) product += (a * b) } nextMatch++ } } } fmt.Println(product) }I also used Go - my solution for part 1 was essentially identical to yours. I went a different route for part 2 that I think ended up being simpler though.
I just prepended
do()anddon't()to the original regex with a|, that way it captured all 3 in order and I just looped through all the matches once and toggled theisEnabledflag accordingly.Always interesting to see how other people tackle the same problem!
Part 2 Code
func part2() { filePath := "input.txt" file, _ := os.Open(filePath) defer file.Close() pattern := regexp.MustCompile(`do\(\)|don't\(\)|mul\((\d{1,3}),(\d{1,3})\)`) productSum := 0 isEnabled := true scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() matches := pattern.FindAllStringSubmatch(line, -1) for _, match := range matches { if match[0] == "do()" { isEnabled = true } else if match[0] == "don't()" { isEnabled = false } else if isEnabled && len(match) == 3 { n, _ := strconv.Atoi(match[1]) m, _ := strconv.Atoi(match[2]) productSum += n * m } } } fmt.Println("Total: ", productSum) }
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"; }Python
def process(input, part2=False): if part2: input = re.sub(r'don\'t\(\).+?do\(\)', '', input) # remove everything between don't() and do() total = [ int(i[0]) * int(i[1]) for i in re.findall(r'mul\((\d+),(\d+)\)', input) ] return sum(total)Given the structure of the input file, we just have to ignore everything between don’t() and do(), so remove those from the instructions before processing.
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 }) }Rust
use crate::utils::read_lines; pub fn solution1() { let lines = read_lines("src/day3/input.txt"); let sum = lines .map(|line| { let mut sum = 0; let mut command_bytes = Vec::new(); for byte in line.bytes() { match (byte, command_bytes.as_slice()) { (b')', [.., b'0'..=b'9']) => { handle_mul(&mut command_bytes, &mut sum); } _ if matches_mul(byte, &command_bytes) => { command_bytes.push(byte); } _ => { command_bytes.clear(); } } } sum }) .sum::<usize>(); println!("Sum of multiplication results = {sum}"); } pub fn solution2() { let lines = read_lines("src/day3/input.txt"); let mut can_mul = true; let sum = lines .map(|line| { let mut sum = 0; let mut command_bytes = Vec::new(); for byte in line.bytes() { match (byte, command_bytes.as_slice()) { (b')', [.., b'0'..=b'9']) if can_mul => { handle_mul(&mut command_bytes, &mut sum); } (b')', [b'd', b'o', b'(']) => { can_mul = true; command_bytes.clear(); } (b')', [.., b't', b'(']) => { can_mul = false; command_bytes.clear(); } _ if matches_do_or_dont(byte, &command_bytes) || matches_mul(byte, &command_bytes) => { command_bytes.push(byte); } _ => { command_bytes.clear(); } } } sum }) .sum::<usize>(); println!("Sum of enabled multiplication results = {sum}"); } fn matches_mul(byte: u8, command_bytes: &[u8]) -> bool { matches!( (byte, command_bytes), (b'm', []) | (b'u', [.., b'm']) | (b'l', [.., b'u']) | (b'(', [.., b'l']) | (b'0'..=b'9', [.., b'(' | b'0'..=b'9' | b',']) | (b',', [.., b'0'..=b'9']) ) } fn matches_do_or_dont(byte: u8, command_bytes: &[u8]) -> bool { matches!( (byte, command_bytes), (b'd', []) | (b'o', [.., b'd']) | (b'n', [.., b'o']) | (b'\'', [.., b'n']) | (b'(', [.., b'o' | b't']) | (b't', [.., b'\'']) ) } fn handle_mul(command_bytes: &mut Vec<u8>, sum: &mut usize) { let first_num_index = command_bytes .iter() .position(u8::is_ascii_digit) .expect("Guarunteed to be there"); let comma_index = command_bytes .iter() .position(|&c| c == b',') .expect("Guarunteed to be there."); let num1 = bytes_to_num(&command_bytes[first_num_index..comma_index]); let num2 = bytes_to_num(&command_bytes[comma_index + 1..]); *sum += num1 * num2; command_bytes.clear(); } fn bytes_to_num(bytes: &[u8]) -> usize { bytes .iter() .rev() .enumerate() .map(|(i, digit)| (*digit - b'0') as usize * 10usize.pow(i as u32)) .sum::<usize>() }
Definitely not my prettiest code ever. It would probably look nicer if I used regex or some parsing library, but I took on the self-imposed challenge of not using third party libraries. Also, this is already further than I made it last year!
Uiua
Part 1:
&fras "day3/input.txt" /+≡/×≡⋕≡↘1regex "mul\\((\\d+),(\\d+)\\)"Part 2:
Filter ← ⍜⊜∘≡⋅""⊸⦷°□ .&fras "day3/input.txt" ∧Filter♭regex"don't\\(\\)?(.*?)(?:do\\(\\)|$)" /+≡/×≡⋕≡↘1regex "mul\\((\\d+),(\\d+)\\)"Kotlin
Just the standard Regex stuff. I found this website to be very helpful to write the patterns. (Very useful in general)
fun main() { fun part1(input: List<String>): Int = Regex("""mul\(\d+,\d+\)""").findAll(input.joinToString()).sumOf { with(Regex("""\d+""").findAll(it.value)) { this.first().value.toInt() * this.last().value.toInt() } } fun part2(input: List<String>): Int { var isMultiplyInstructionEnabled = true // by default return Regex("""mul\(\d+,\d+\)|do\(\)|don't\(\)""").findAll(input.joinToString()).fold(0) { acc, instruction -> when (instruction.value) { "do()" -> acc.also { isMultiplyInstructionEnabled = true } "don't()" -> acc.also { isMultiplyInstructionEnabled = false } else -> { if (isMultiplyInstructionEnabled) { acc + with(Regex("""\d+""").findAll(instruction.value)) { this.first().value.toInt() * this.last().value.toInt() } } else acc } } } } val testInputPart1 = readInput("Day03_test_part1") val testInputPart2 = readInput("Day03_test_part2") check(part1(testInputPart1) == 161) check(part2(testInputPart2) == 48) val input = readInput("Day03") part1(input).println() part2(input).println() } ´´´








