this post was submitted on 03 Dec 2024
19 points (100.0% liked)

Advent Of Code

885 readers
133 users here now

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2024

Solution Threads

M T W T F S S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 18 20 21 22
23 24 25

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 1 year ago
MODERATORS
 

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

top 49 comments
sorted by: hot top controversial new old
[–] [email protected] 1 points 2 hours ago

I did part 2 live with the python interactive shell. I deleted all the stuff where I was just exploring ideas.

part 1:

import re

def multiply_and_add(data: "str") -> int:
    digit_matches = re.findall(r"mul\(\d{0,3},\d{0,3}\)", data)
    result = 0
    for _ in digit_matches:
        first = _.split("(")[1].split(")")[0].split(",")[0]
        second = _.split("(")[1].split(")")[0].split(",")[1]
        result += int(first) * int(second)

    return result

with open("input") as file:
    data = file.read()


answer = multiply_and_add(data)
print(answer)

part 2:

Python 3.11.2 (main, Aug 26 2024, 07:20:54) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import solution2
<re.Match object; span=(647, 651), match='do()'>
>>> from solution2 import *
>>> split_on_dont = data.split("don't()")
>>> valid = []
>>> valid.append(split_on_dont[0])
>>> for substring in split_on_dont[1:]:
...     subsubstrings = substring.split("do()", maxsplit=1)
...     for subsubstring in subsubstrings[1:]:
...             valid.append(subsubstring)
...
>>> answer = 0
>>> for _ in valid:
...     answer += multiply_and_add(_)
...
>>> answer
103811193
[–] zarlin 2 points 5 hours ago

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, and re 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.

[–] andMoonsValue 3 points 15 hours ago* (last edited 15 hours ago)

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))
[–] [email protected] 2 points 23 hours ago

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()
}

´´´
[–] [email protected] 3 points 1 day ago

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)
[–] [email protected] 2 points 1 day ago

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 })
}
[–] thirteen37 2 points 1 day ago (1 children)

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.

[–] TunaCowboy 2 points 1 day ago (1 children)

Sub was my first instinct too, but I got a bad answer and saw that my input had unbalanced do/don't.

[–] thirteen37 2 points 1 day ago

I did wonder if that might be the case, I must have been lucky with my input.

[–] [email protected] 1 points 1 day ago

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)

[–] RollingRagdoll 2 points 1 day ago* (last edited 1 day ago)

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+)\\)"
[–] Karmmah 1 points 1 day ago* (last edited 1 day ago)

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
[–] VegOwOtenks 5 points 1 day ago (3 children)

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))
[–] [email protected] 1 points 2 hours ago

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()-

[–] [email protected] 2 points 1 day ago

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

[–] [email protected] 1 points 1 day ago

Nice, sometimes a few extra linebreaks can do the trick...

[–] [email protected] 3 points 1 day ago* (last edited 9 hours ago)

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 ;
[–] [email protected] 6 points 1 day ago

Sorry for the delay posting this one, Ategon seemed to have it covered, so I forgot :D I will do better.

[–] [email protected] 3 points 1 day ago (1 children)

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)
}

[–] [email protected] 2 points 1 day ago (1 children)

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() and don'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 the isEnabled 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)
}

[–] [email protected] 1 points 1 day ago

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

[–] TunaCowboy 1 points 1 day ago* (last edited 1 day ago)

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()

[–] LeixB 5 points 1 day ago (1 children)

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)
[–] [email protected] 2 points 1 day ago

Of course it's point-free

[–] [email protected] 5 points 1 day ago* (last edited 1 day ago) (1 children)

C

Yay parsers! I've gotten quite comfortable writing these with C. Using out pointers arguments for the cursor that are only updated if the match is successful makes for easy bookkeeping.

Code

#include "common.h"

static int
parse_exact(const char **stringp, const char *expect)
{
	const char *s = *stringp;
	int i;

	for (i=0; s[i] && expect[i] && s[i] == expect[i]; i++)
		;
	if (expect[i])
		return 0;

	*stringp  = &s[i];
	return 1;
}

static int
parse_int(const char **stringp, int *outp)
{
	char *end;
	int val;

	val = (int)strtol(*stringp, &end, 10);
	if (end == *stringp)
		return 0;

	*stringp = end;
	if (outp) *outp = val;
	return 1;
}

static int
parse_mul(const char **stringp, int *ap, int *bp)
{
	const char *cur = *stringp;
	int a,b;

	if (!parse_exact(&cur, "mul(") ||
	    !parse_int(&cur, &a) ||
	    !parse_exact(&cur, ",") ||
	    !parse_int(&cur, &b) ||
	    !parse_exact(&cur, ")"))
		return 0;

	*stringp = cur;
	if (ap) *ap = a;
	if (bp) *bp = b;
	return 1;
}

int
main(int argc, char **argv)
{
	static char buf[32*1024];
	const char *cur;
	size_t nr;
	int p1=0,p2=0, a,b, dont=0;

	if (argc > 1)
		DISCARD(freopen(argv[1], "r", stdin));

	nr = fread(buf, 1, sizeof(buf), stdin);
	assert(!ferror(stdin));
	assert(nr != sizeof(buf));
	buf[nr] = '\0';

	for (cur = buf; *cur; )
		if (parse_exact(&cur, "do()"))
			dont = 0;
		else if (parse_exact(&cur, "don't()"))
			dont = 1;
		else if (parse_mul(&cur, &a, &b)) {
			p1 += a * b;
			if (!dont) p2 += a * b;
		} else
			cur++;

	printf("03: %d %d\n", p1, p2);
}

https://github.com/sjmulder/aoc/blob/master/2024/c/day03.c

[–] [email protected] 2 points 1 day ago

Got the code a little shorter:

Code

#include "common.h"

static int
parse_mul(const char **stringp, int *ap, int *bp)
{
	const char *cur = *stringp, *end;

	if (strncmp(cur, "mul(", 4)) { return 0; } cur += 4;
	*ap = (int)strtol(cur, (char **)&end, 10);
	if (end == cur)  { return 0; } cur = end;
	if (*cur != ',') { return 0; } cur += 1;
	*bp = (int)strtol(cur, (char **)&end, 10);
	if (end == cur)  { return 0; } cur = end;
	if (*cur != ')') { return 0; } cur += 1;

	*stringp = cur;
	return 1;
}

int
main(int argc, char **argv)
{
	static char buf[32*1024];
	const char *p;
	size_t nr;
	int p1=0,p2=0, a,b, dont=0;

	if (argc > 1)
		DISCARD(freopen(argv[1], "r", stdin));

	nr = fread(buf, 1, sizeof(buf), stdin);
	assert(!ferror(stdin));
	assert(nr != sizeof(buf));
	buf[nr] = '\0';

	for (p = buf; *p; )
		if (parse_mul(&p, &a, &b)) { p1 += a*b; p2 += a*b*!dont; }
		else if (!strncmp(p, "do()", 4))    { dont = 0; p += 4; }
		else if (!strncmp(p, "don't()", 7)) { dont = 1; p += 7; }
		else p++;

	printf("03: %d %d\n", p1, p2);
}

[–] [email protected] 5 points 1 day ago* (last edited 1 day ago) (1 children)

Nim

From a first glance it was obviously a regex problem.
I'm using tinyre here instead of stdlib re 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

Codeberg repo

[–] [email protected] 4 points 1 day ago

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!

[–] vole 2 points 1 day ago* (last edited 1 day ago)

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";
}

github

[–] [email protected] 3 points 1 day ago (1 children)

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)
}
[–] [email protected] 1 points 1 day ago

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.

[–] [email protected] 4 points 1 day ago

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}");
}

[–] mykl 4 points 1 day ago* (last edited 1 day ago)

Uiua

Uses experimental feature of fold to 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.
[–] [email protected] 3 points 1 day ago* (last edited 1 day ago) (1 children)

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
}
[–] [email protected] 3 points 1 day ago

You can avoid having to escape the backslashes in regexps by using multiline strings:

val pattern = """mul\((\d{1,3}),(\d{1,3})\)""".toRegex()
[–] rwdf 2 points 1 day ago* (last edited 1 day ago) (1 children)

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()}")

[–] vole 3 points 1 day ago (1 children)

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().

[–] rwdf 1 points 1 day ago (1 children)

Ah, yes, that's it. The lazy solution would be to add a "do()" to the end of the input, right? Haha

[–] rwdf 1 points 1 day ago

It was actually a line break that broke the regex. Changing from a "." to "[\s\S]" fixed it.

[–] [email protected] 4 points 1 day ago (1 children)

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

[–] VegOwOtenks 2 points 1 day ago (1 children)

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 of Mul !Int !Int?

[–] [email protected] 3 points 1 day ago (1 children)

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.

[–] [email protected] 2 points 1 day ago

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.

[–] [email protected] 2 points 1 day ago

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!

[–] [email protected] 2 points 1 day ago (1 children)

Rust

Regex made this one pretty straightforward. The second part additionally looks for do() and don't() in the same regex, then we do a case distinction on the match.

use regex::{Regex, Captures};

fn mul_cap(cap: Captures) -> i32 {
    let a = cap.get(1).unwrap().as_str().parse::<i32>().unwrap();
    let b = cap.get(2).unwrap().as_str().parse::<i32>().unwrap();
    a * b
}

fn part1(input: String) {
    let re = Regex::new(r"mul\((\d{1,3}),(\d{1,3})\)").unwrap();
    let res = re.captures_iter(&input).map(mul_cap).sum::<i32>();
    println!("{res}");
}

fn part2(input: String) {
    let re = Regex::new(r"do\(\)|don't\(\)|mul\((\d{1,3}),(\d{1,3})\)").unwrap();
    let mut enabled = true;
    let mut res = 0;
    for cap in re.captures_iter(&input) {
        match cap.get(0).unwrap().as_str() {
            "do()" => enabled = true,
            "don't()" => enabled = false,
            _ if enabled => res += mul_cap(cap),
            _ => {}
        }
    }
    println!("{res}");
}

util::aoc_main!();
[–] [email protected] 1 points 1 day ago

Our part1's are basically identical, but for part 2 I just regex deleted everything between dont() anddo() `, after sanitizing out the newlines.

I guess it would have failed if my test data had a mul after the last dont, but it worked out, so good enough :)

[–] [email protected] 1 points 1 day ago

C#

public partial class Day03 : Solver
{
  [GeneratedRegex(@"mul[(](\d{1,3}),(\d{1,3})[)]")]
  private partial Regex mulRegex();

  [GeneratedRegex(@"(do)[(][)]|(don't)[(][)]|(mul)[(](\d{1,3}),(\d{1,3})[)]")]
  private partial Regex fullRegex();

  private string input;

  public void Presolve(string input)
  {
    this.input = input.Trim();
  }

  public string SolveFirst() => mulRegex().Matches(input)
      .Select(match => int.Parse(match.Groups[1].Value) * int.Parse(match.Groups[2].Value))
      .Sum().ToString();

  public string SolveSecond()
  {
    bool enabled = true;
    int sum = 0;
    foreach (Match match in fullRegex().Matches(input)) {
      if (match.Groups[1].Length > 0) {
        enabled = true;
      } else if (match.Groups[2].Length > 0) {
        enabled = false;
      } else if (enabled) {
        sum += int.Parse(match.Groups[4].Value) * int.Parse(match.Groups[5].Value);
      }
    }
    return sum.ToString();
  }
}
[–] [email protected] 1 points 1 day ago

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 :)

[–] [email protected] 1 points 1 day ago* (last edited 17 hours ago)

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(())
}