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

  • Hammerheart@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    3 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@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    6 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@lemmy.world
    link
    fedilink
    arrow-up
    3
    ·
    edit-2
    16 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))
    
  • the_beber@lemm.ee
    link
    fedilink
    English
    arrow-up
    2
    ·
    24 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()
    }
    
    ´´´
  • urquell@lemm.ee
    link
    fedilink
    arrow-up
    3
    ·
    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)
    
  • bugsmith@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    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 })
    }
    
  • Andy@programming.dev
    link
    fedilink
    arrow-up
    4
    ·
    edit-2
    10 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 ;
    
  • thirteen37@lemmy.world
    link
    fedilink
    English
    arrow-up
    2
    ·
    1 day ago

    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.

  • Sparrow_1029@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    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@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    1 day ago

    Uiua

    Part 1:

    &fras "day3/input.txt"
    /+≡/×≡⋕≡↘1regex "mul\\((\\d+),(\\d+)\\)"
    

    Part 2:

    Filter  ⍜⊜∘≡⋅""⊸⦷°□
    .&fras "day3/input.txt"
    Filterregex"don't\\(\\)?(.*?)(?:do\\(\\)|$)"
    /+≡/×≡⋕≡↘1regex "mul\\((\\d+),(\\d+)\\)"
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    5
    ·
    2 days ago

    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))
    
    • Hammerheart@programming.dev
      link
      fedilink
      arrow-up
      1
      ·
      3 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()-

    • reboot6675@sopuli.xyz
      link
      fedilink
      arrow-up
      2
      ·
      2 days 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

  • Karmmah@lemmy.world
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    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
    
  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    3
    ·
    2 days ago

    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 recent don't(); for part 2 a multiplication is enabled if the position of the most recent do() (counting start of input as 0) is greater than that of the most recent don'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 =: (&lt;0 0) &amp; {"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) &amp; -) @: {:)
    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
    
  • reboot6675@sopuli.xyz
    link
    fedilink
    arrow-up
    3
    ·
    2 days ago

    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)
    }
    
    • gothink@lemmy.ca
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 day ago

      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)
      }
      
      • reboot6675@sopuli.xyz
        link
        fedilink
        arrow-up
        1
        ·
        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