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

  • LeixB@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    13 days ago

    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)
    
  • janAkali@lemmy.one
    link
    fedilink
    English
    arrow-up
    0
    ·
    edit-2
    13 days ago

    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

    • sjmulder@lemmy.sdf.org
      link
      fedilink
      arrow-up
      0
      ·
      13 days 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!

  • lwhjp@lemmy.sdf.org
    link
    fedilink
    arrow-up
    0
    ·
    13 days ago

    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@lemmy.world
      link
      fedilink
      arrow-up
      0
      ·
      12 days ago

      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?

      • kintrix@linux.community
        link
        fedilink
        English
        arrow-up
        0
        ·
        12 days ago

        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.

        • lwhjp@lemmy.sdf.org
          link
          fedilink
          arrow-up
          0
          ·
          12 days 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.

  • Andy@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    12 days ago

    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 ;
    
  • vole@lemmy.world
    link
    fedilink
    English
    arrow-up
    0
    ·
    edit-2
    12 days 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

  • reboot6675@sopuli.xyz
    link
    fedilink
    arrow-up
    0
    ·
    12 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
      0
      ·
      12 days 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)
      }
      
  • urquell@lemm.ee
    link
    fedilink
    arrow-up
    0
    ·
    12 days 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)
    
  • thirteen37@lemmy.world
    link
    fedilink
    English
    arrow-up
    0
    ·
    12 days 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.

  • Sleepless One@lemmy.ml
    link
    fedilink
    English
    arrow-up
    0
    ·
    12 days 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!

  • bugsmith@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    12 days 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 })
    }
    
  • RollingRagdoll@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    12 days 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+)\\)"
    
  • the_beber@lemm.ee
    link
    fedilink
    English
    arrow-up
    0
    ·
    12 days 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()
    }
    
    ´´´
  • andMoonsValue@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    11 days 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))
    
  • Zarlin@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    11 days 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.