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

  • bugsmith@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    2 months 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 })
    }
    
  • janAkali@lemmy.one
    link
    fedilink
    English
    arrow-up
    0
    ·
    edit-2
    2 months 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
      ·
      2 months 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
    ·
    2 months 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
      ·
      2 months 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
        ·
        2 months 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
          ·
          2 months 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.

  • andMoonsValue@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    2 months 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))
    
  • Sleepless One@lemmy.ml
    link
    fedilink
    English
    arrow-up
    0
    ·
    2 months 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!

  • the_beber@lemm.ee
    link
    fedilink
    English
    arrow-up
    0
    ·
    2 months 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()
    }
    
    ´´´
  • RollingRagdoll@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    2 months 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+)\\)"
    
  • Ananace@lemmy.ananace.dev
    link
    fedilink
    arrow-up
    0
    ·
    2 months 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}");
    }
    
  • madmo@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    2 months ago

    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)
    }
    
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    2 months ago

    Uiua

    Uses (abuses?) experimental features of fold to track the running state of do/don’t state. Although it works well and fast, I don’t think I would recommend this code to anyone :-) Try it if you must!

    # 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)regex GetMul DataP₁ # Part 1
    
    # Build an accumulator to track running state of do/don't
    Filter ← ≡⋕↘1◌∧(⍣("0" °"n"|"1" °"("|.◌)) :"1"∵◇⊡2
    
    regex GetMulDoDont DataP₂
    ▽⊸≡(¬≍"do()"°□⊢)▽⊸Filter     # Apply Filter, remove the spare 'do's
    &p/+≡(/×≡⋕↘1⊢regexGetMul°□⊢) # Get the digits and multiply, sum.
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    0
    ·
    2 months 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
      0
      ·
      2 months 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

  • proved_unglue@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    2 months ago

    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
    }
    
    • Faresh@lemmy.ml
      link
      fedilink
      English
      arrow-up
      0
      ·
      2 months 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()
      
  • Gobbel2000@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    2 months ago

    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!();
    
  • sjmulder@lemmy.sdf.org
    link
    fedilink
    English
    arrow-up
    0
    ·
    2 months 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
      0
      ·
      2 months 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);
      }