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
Uiua
Regex my beloved <3
Run with example input here
FindMul ← regex "mul\\((\\d+),(\\d+)\\)" PartOne ← ( &rs ∞ &fo "input-3.txt" FindMul /+≡(×°⊟⋕⊏1_2) ) IdDont ← ⊗□"don't()"♭ PartTwo ← ( &rs ∞ &fo "input-3.txt" regex "mul\\(\\d+,\\d+\\)|do\\(\\)|don't\\(\\)" ⍢(IdDont. ↘1⊃↘↙ ⊗□"do()"♭. ⊂↘1↘ | IdDont. ≠⧻, ) ▽♭=0⌕□"do()". ≡(×°⊟⋕⊏1_2♭FindMul)♭ /+ ) &p "Day 3:" &pf "Part 1: " &p PartOne &pf "Part 2: " &p PartTwo
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"; }
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.
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+)\\)"
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 }) }
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.
Sub was my first instinct too, but I got a bad answer and saw that my input had unbalanced do/don’t.
I did wonder if that might be the case, I must have been lucky with my input.
python
solution
import re import aoc def setup(): return (aoc.get_lines(3), 0) def one(): lines, acc = setup() for line in lines: ins = re.findall(r'mul\(\d+,\d+\)', line) for i in ins: p = [int(x) for x in re.findall(r'\d+', i)] acc += p[0] * p[1] print(acc) def two(): lines, acc = setup() on = 1 for line in lines: ins = re.findall(r"do\(\)|don't\(\)|mul\(\d+,\d+\)", line) for i in ins: if i == "do()": on = 1 elif i == "don't()": on = 0 elif on: p = [int(x) for x in re.findall(r'\d+', i)] acc += p[0] * p[1] print(acc) one() two()
Julia
I did not try to make my solution concise and kept separate code for part 1 and part 2 with test cases for both to check if I broke anything. But after struggling with Day 2 I am quite pleased to have solved Day 3 with only a little bugfixing.
function calcLineResult(line::String) lineResult::Int = 0 enabled::Bool = true for i=1 : length(line) line[i]!='m' ? continue : (i<length(line) ? i+=1 : continue) line[i]!='u' ? continue : (i<length(line) ? i+=1 : continue) line[i]!='l' ? continue : (i<length(line) ? i+=1 : continue) line[i]!='(' ? continue : (i<length(line) ? i+=1 : continue) num1Str::String = "" while line[i] in ['0','1','2','3','4','5','6','7','8','9'] #should check for length of digits < 3, but works without num1Str = num1Str*line[i]; (i<length(line) ? i+=1 : continue) end line[i]!=',' ? continue : (i<length(line) ? i+=1 : continue) num2Str::String = "" while line[i] in ['0','1','2','3','4','5','6','7','8','9'] #should check for length of digits < 3, but works without num2Str = num2Str*line[i]; (i<length(line) ? i+=1 : continue) end line[i]==')' ? lineResult+=parse(Int,num1Str)*parse(Int,num2Str) : continue end return lineResult end function calcLineResultWithEnabling(line::String,enabled::Bool) lineResult::Int = 0 for i=1 : length(line) if enabled && line[i] == 'm' i<length(line) ? i += 1 : continue line[i]!='u' ? continue : (i<length(line) ? i+=1 : continue) line[i]!='l' ? continue : (i<length(line) ? i+=1 : continue) line[i]!='(' ? continue : (i<length(line) ? i+=1 : continue) num1Str::String = "" while line[i] in ['0','1','2','3','4','5','6','7','8','9'] num1Str = num1Str*line[i]; (i<length(line) ? i+=1 : continue) end line[i]!=',' ? continue : (i<length(line) ? i+=1 : continue) num2Str::String = "" while line[i] in ['0','1','2','3','4','5','6','7','8','9'] num2Str = num2Str*line[i]; (i<length(line) ? i+=1 : continue) end line[i]==')' ? lineResult+=parse(Int,num1Str)*parse(Int,num2Str) : continue elseif line[i] == 'd' i<length(line) ? i += 1 : continue line[i]!='o' ? continue : (i<length(line) ? i+=1 : continue) if line[i] == '(' i<length(line) ? i += 1 : continue line[i]==')' ? enabled=true : continue #@info i,line[i-3:i] elseif line[i] == 'n' i<length(line) ? i += 1 : continue line[i]!=''' ? continue : (i<length(line) ? i+=1 : continue) line[i]!='t' ? continue : (i<length(line) ? i+=1 : continue) line[i]!='(' ? continue : (i<length(line) ? i+=1 : continue) line[i]==')' ? enabled=false : continue #@info i,line[i-6:i] else nothing end end end return lineResult,enabled end function calcMemoryResult(inputFile::String,useEnabling::Bool) memoryRes::Int = 0 f = open(inputFile,"r") lines = readlines(f) close(f) enabled::Bool = true for line in lines if useEnabling lineRes::Int,enabled = calcLineResultWithEnabling(line,enabled) memoryRes += lineRes else memoryRes += calcLineResult(line) end end return memoryRes end if abspath(PROGRAM_FILE) == @__FILE__ @info "Part 1" @debug "checking test input" inputFile::String = "day03InputTest" memoryRes::Int = calcMemoryResult(inputFile,false) try @assert memoryRes==161 catch e throw(ErrorException("$e memoryRes=$memoryRes")) end @debug "test input ok" @debug "running real input" inputFile::String = "day03Input" memoryRes::Int = calcMemoryResult(inputFile,false) try @assert memoryRes==153469856 catch e throw(ErrorException("$e memoryRes=$memoryRes")) end println("memory result: $memoryRes") @debug "real input ok" @info "Part 2" @debug "checking test input" inputFile::String = "day03InputTest" memoryRes::Int = calcMemoryResult(inputFile,true) try @assert memoryRes==48 catch e throw(ErrorException("$e memoryRes=$memoryRes")) end @debug "test input ok" @debug "running real input" inputFile::String = "day03Input" memoryRes::Int = calcMemoryResult(inputFile,true) try @assert memoryRes==77055967 catch e throw(ErrorException("$e memoryRes=$memoryRes")) end println("memory result: $memoryRes") @debug "real input ok" end
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)
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() } ´´´
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(()) }
deleted by creator
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)
Factor
: get-input ( -- corrupted-input ) "vocab:aoc-2024/03/input.txt" utf8 file-contents ; : get-muls ( corrupted-input -- instructions ) R/ mul\(\d+,\d+\)/ all-matching-subseqs ; : process-mul ( instruction -- n ) R/ \d+/ all-matching-subseqs [ string>number ] map-product ; : solve ( corrupted-input -- n ) get-muls [ process-mul ] map-sum ; : part1 ( -- n ) get-input solve ; : part2 ( -- n ) get-input R/ don't\(\)(.|\n)*?do\(\)/ split concat R/ don't\(\)(.|\n)*/ "" re-replace solve ;
Nim
import ../aoc, re, sequtils, strutils, math proc mulsum*(line:string):int= let matches = line.findAll(re"mul\([0-9]{1,3},[0-9]{1,3}\)") let pairs = matches.mapIt(it[4..^2].split(',').map(parseInt)) pairs.mapIt(it[0]*it[1]).sum proc filter*(line:string):int= var state = true; var i=0 while i < line.len: if state: let off = line.find("don't()", i) if off == -1: break result += line[i..<off].mulsum i = off+6 state = false else: let on = line.find("do()", i) if on == -1: break i = on+4 state = true if state: result += line[i..^1].mulsum proc solve*(input:string): array[2,int] = #part 1&2 result = [input.mulsum, input.filter]
I had a nicer solution in mind for part 2, but for some reason
nre
didn’t want to work for me, andre
couldn’t give me the start/end or all results, so I ended up doing this skip/toggle approach.Also initially I was doing it line by line out of habit from other puzzles, but then ofc the
don't()
s didn’t propagate to the next line.