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

  • Quant@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    5 days ago

    Uiua

    Regex my beloved <3

    Run with example input here

    FindMul  regex "mul\\((\\d+),(\\d+)\\)"
    
    PartOne  (
      &rs  &fo "input-3.txt"
      FindMul
      /+≡(×°⊟⋕⊏1_2)
    )
    
    IdDont  ⊗□"don't()"
    
    PartTwo  (
      &rs  &fo "input-3.txt"
      regex "mul\\(\\d+,\\d+\\)|do\\(\\)|don't\\(\\)"
      (IdDont.
        1⊃↘↙
        ⊗□"do()".
        ⊂↘1
      | IdDont.
        ≠⧻,
      )
      ▽♭=0⌕□"do()".
      (×°⊟⋕⊏1_2FindMul)
      /+
    )
    
    &p "Day 3:"
    &pf "Part 1: "
    &p PartOne
    &pf "Part 2: "
    &p PartTwo
    
  • wer2@lemm.ee
    link
    fedilink
    arrow-up
    1
    ·
    5 days ago

    Lisp

    Just did some basic regex stuff.

    Part 1 and 2
    
    (defun p1-mult (str)
      "pulls out numbers and multiplies them, assumes already filtered by size"
      (let ((vals (ppcre:all-matches-as-strings "\\d+" str)))
        (apply #'* (or (mapcar #'parse-integer vals) '(0)))))
    
    (defun p1-process-line (line)
      "look for mul, do the mul, and sum"
      (let ((ptrn "mul\\(\\d?\\d?\\d,\\d?\\d?\\d\\)"))
        (apply #'+ (mapcar #'p1-mult (ppcre:all-matches-as-strings ptrn line)))))
    
    (defun run-p1 (file) 
      (let ((data (read-file file #'p1-process-line)))
        (apply #'+ data)))
    
    (defun p2-process-line (line)
      "looks for mul, do, and don't"
      (let ((ptrn "(mul\\(\\d?\\d?\\d,\\d?\\d?\\d\\))|(do\\(\\))|(don't\\(\\))"))
        (ppcre:all-matches-as-strings ptrn line)))
    
    (defun p2-filter (data)
      "expects list containing the string tokens (mul, do, don't) from the file"
      (let ((process t))
        (loop for x in data
              when (string= "don't()" x)
                do (setf process nil)
              when (string= "do()" x)
                do (setf process t)
              when process
                sum (p1-mult x))))
    
    (defun run-p2 (file) 
      (let ((data (read-file file #'p2-process-line)))
        ;; treat the input as one line to make processing the do's and don't's easier
        (p2-filter (flatten data))))
    
    
  • Chais@sh.itjust.works
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    7 days ago

    Python

    After a bunch of fiddling yesterday and today I finally managed to arrive at a regex-only solution for part 2. That re.DOTALL is crucial here.

    import re
    from pathlib import Path
    
    
    def parse_input_one(input: str) -> list[tuple[int]]:
        p = re.compile(r"mul\((\d{1,3}),(\d{1,3})\)")
        return [(int(m[0]), int(m[1])) for m in p.findall(input)]
    
    
    def parse_input_two(input: str) -> list[tuple[int]]:
        p = re.compile(r"don't\(\).*?do\(\)|mul\((\d{1,3}),(\d{1,3})\)", re.DOTALL)
        return [(int(m[0]), int(m[1])) for m in p.findall(input) if m[0] and m[1]]
    
    
    def part_one(input: str) -> int:
        pairs = parse_input_one(input)
        return sum(map(lambda v: v[0] * v[1], pairs))
    
    
    def part_two(input: str) -> int:
        pairs = parse_input_two(input)
        return sum(map(lambda v: v[0] * v[1], pairs))
    
    
    if __name__ == "__main__":
        input = Path("input").read_text("utf-8")
        print(part_one(input))
        print(part_two(input))
    
  • aurele@sh.itjust.works
    link
    fedilink
    arrow-up
    1
    ·
    6 days ago

    Elixir

    defmodule AdventOfCode.Solution.Year2024.Day03 do
      def part1(input) do
        Regex.scan(~r/mul\((?<l>\d+),(?<r>\d+)\)/, input, capture: ["l", "r"])
        |> Stream.map(fn l -> Enum.reduce(l, 1, &(&2 * String.to_integer(&1))) end)
        |> Enum.sum()
      end
    
      def part2(input) do
        input |> String.replace(~r/don't\(\).*(do\(\)|$)/Us, "") |> part1
      end
    end
    
  • morrowind@lemmy.ml
    link
    fedilink
    arrow-up
    1
    ·
    7 days ago

    Smalltalk

    I wrote matchesActual cause all of smalltalk’sstupid matchesDo: or whatever don’t give you the actual match with captures, only substrings (that wasted a good 40 minutes).

    Also smalltalk really needs an index operator

    day3p1: input
      | reg sum |
        reg := 'mul\((\d\d?\d?),(\d\d?\d?)\)' asRegex.
        sum := 0.
        
        reg matchesActual: input do: [ :m | " + sum at end cause operator precedence"
            sum := (m subexpression: 2) asInteger * (m subexpression: 3) asInteger + sum 
        ].
        
        ^ sum.
    
    day3p2: input
      | reg sum do val |
    
        reg := 'do(\:?n''t)?\(\)|mul\((\d{1,3}),(\d{1,3})\)' asRegex.
        sum := 0.
        do := true.
        reg matchesActual: input do: [ :m |
            val := m subexpression: 1.
            (val at: 1) = $d ifTrue: [ do := (val size < 5) ]
            ifFalse: [ 
                do ifTrue: [ 
                    sum := (m subexpression: 2) asInteger * (m subexpression: 3) asInteger + sum.
            ].  ].
        ].
        
        ^ sum.
    
  • janAkali@lemmy.one
    link
    fedilink
    English
    arrow-up
    5
    ·
    edit-2
    9 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
      4
      ·
      9 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!

  • LeixB@lemmy.world
    link
    fedilink
    arrow-up
    5
    ·
    9 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)
    
  • Ananace@lemmy.ananace.dev
    link
    fedilink
    arrow-up
    5
    ·
    9 days 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}");
    }
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    5
    ·
    9 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))
    
    • reboot6675@sopuli.xyz
      link
      fedilink
      arrow-up
      2
      ·
      9 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

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

  • sjmulder@lemmy.sdf.org
    link
    fedilink
    English
    arrow-up
    5
    ·
    edit-2
    9 days ago

    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

    • sjmulder@lemmy.sdf.org
      link
      fedilink
      arrow-up
      2
      ·
      8 days 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);
      }
      
  • urquell@lemm.ee
    link
    fedilink
    arrow-up
    4
    ·
    8 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)
    
  • lwhjp@lemmy.sdf.org
    link
    fedilink
    arrow-up
    4
    ·
    9 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
      2
      ·
      9 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
        3
        ·
        8 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
          3
          ·
          8 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.

  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    4
    ·
    edit-2
    9 days 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.
    
  • Andy@programming.dev
    link
    fedilink
    arrow-up
    4
    ·
    edit-2
    7 days 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 ;