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
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
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)
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 }) }
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
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
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)
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()
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+)\\)"
Factor
: get-input ( -- corrupted-input ) "aoc-2024.03" "input.txt" vocab-file-path 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 ;
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
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"; }
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
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…
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.
Rust with nom parser
Decided to give it a go with the nom parser (first time using this crate). Turned out quite nicely. Had some issues with the alt combinator: All alternatives have to return the same type, using a enum to wrap all options did the trick.
use memmap2::Mmap; use nom::{ branch::alt, bytes::complete::*, character::complete::*, combinator::map, multi::many_till, sequence::tuple, AsBytes, IResult, }; #[derive(Debug)] enum Token { Do, Dont, Mul(u64, u64), } fn main() -> anyhow::Result<()> { let file = std::fs::File::open("input.txt")?; let mmap = unsafe { Mmap::map(&file)? }; let mut sum_part1 = 0; let mut sum_part2 = 0; let mut enabled = true; let mut cursor = mmap.as_bytes(); while let Ok(token) = parse(cursor) { match token.1 .1 { Token::Do => enabled = true, Token::Dont => enabled = false, Token::Mul(left, right) => { let prod = left * right; sum_part1 += prod; if enabled { sum_part2 += prod; } } } cursor = token.0; } println!("part1: {} part2: {}", sum_part1, sum_part2); Ok(()) } type ParseResult<'a> = Result<(&'a [u8], (Vec<char>, Token)), nom::Err<nom::error::Error<&'a [u8]>>>; fn parse(input: &[u8]) -> ParseResult { many_till( anychar, alt(( map(doit, |_| Token::Do), map(dont, |_| Token::Dont), map(mul, |el| Token::Mul(el.2, el.4)), )), )(input) } fn doit(input: &[u8]) -> IResult<&[u8], &[u8]> { tag("do()")(input) } fn dont(input: &[u8]) -> IResult<&[u8], &[u8]> { tag("don't()")(input) } type ParsedMulResult<'a> = (&'a [u8], &'a [u8], u64, &'a [u8], u64, &'a [u8]); fn mul(input: &[u8]) -> IResult<&[u8], ParsedMulResult> { tuple((tag("mul"), tag("("), u64, tag(","), u64, tag(")")))(input) }
doit
I had to check if
do
was reserved since I couldn’t remember seeing it in the language, and it’s in the reserved for future use section.