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
Python
I’m surprised I don’t see more people taking advantage of
eval
I thought it was pretty slick.import operator import re with open('input.txt', 'r') as file: memory = file.read() matches = re.findall("mul\(\d{1,3},\d{1,3}\)|don't\(\)|do\(\)", memory) enabled = 1 filtered_matches = [] for instruction in matches: if instruction == "don't()": enabled = 0 continue elif instruction == "do()": enabled = 1 continue elif enabled: filtered_matches.append(instruction) multipled = [eval(f"operator.{x}") for x in filtered_matches] print(sum(multiples))
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() } ´´´
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)
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
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)
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.
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
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 ;
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+)\\)"
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()
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))
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
Nice, sometimes a few extra linebreaks can do the trick…
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
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 theisEnabled
flag 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) }
Honestly this is soo much better, I’m not proud of my code at all haha. Thanks for sharing, definitely adding that | to my bag of tricks
Sorry for the delay posting this one, Ategon seemed to have it covered, so I forgot :D I will do better.
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