Day 3: Gear Ratios


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)
  • Code block support is not fully rolled out yet but likely will be in the middle of the event. Try to share solutions as both code blocks and using something such as https://topaz.github.io/paste/ or pastebin (code blocks to future proof it for when 0.19 comes out and since code blocks currently function in some apps and some instances as well if they are running a 0.19 beta)

FAQ


🔒This post will be unlocked when there is a decent amount of submissions on the leaderboard to avoid cheating for top spots

🔓 Edit: Post has been unlocked after 11 minutes

    • Gobbel2000@feddit.de
      link
      fedilink
      arrow-up
      2
      ·
      7 months ago

      I like it, it’s simple and to the point. I’ve learned that one of the most helpful things to do when solving these puzzles is to not make it more complicated than it needs to be, and you certainly succeeded better at that today than I did.

      • cacheson@kbin.social
        link
        fedilink
        arrow-up
        1
        ·
        7 months ago

        My solution for day 1 part 1 was simple and to the point. The other ones are getting increasingly less so. You’re right that sometimes it’s best not to get too fancy, but I think soon I may have to break out such advanced programming techniques as “functions” and maybe “objects”, instead of writing increasingly convoluted piles of nested loops. xD

  • Gobbel2000@feddit.de
    link
    fedilink
    arrow-up
    5
    ·
    7 months ago

    Rust

    I’ve been using Regexes for every day so far, this time it helped in finding numbers along with their start and end position in a line. For the second part I mostly went with the approach of part 1 which was to look at all numbers and then figure out if it has a part symbol around it. Only in part 2 I saved all numbers next to a gear * in a hash table that maps each gear position to a list of adjacent numbers. Then in the end I can just look at all gears with exactly 2 numbers attached.

    Also it has to be said, multiplying two numbers is the exact opposite of getting their ratio!

  • Nighed@sffa.community
    link
    fedilink
    English
    arrow-up
    3
    ·
    edit-2
    7 months ago

    Language: C#

    I aimed at keeping it as simple and short as reasonably possible this time, no overbuilding here!

    I even used a goto to let me break out of multiple loops at once 🤮 (I had to look up how they worked!) I would totally fail me in a code review!

    One solution for both
    internal class Day3 : IRunnable
        {
            public void Run()
            {
                var input = File.ReadAllLines("Days/Three/Day3Input.txt");
                int sum = 0;
                string numStr = "";
                var starMap = new Dictionary<(int,int),List>();
                for (int i = 0; i < input.Length; i++)           
                    for (int j = 0; j < input[i].Length; j++)
                    {
                        if (char.IsDigit(input[i][j]))                    
                            numStr += input[i][j];                    
                        if (numStr.Length > 0 && (j == input[i].Length - 1 || !char.IsDigit(input[i][j + 1])))
                        {
                            for (int k = Math.Max(0, i - 1); k < Math.Min(i + 2, input.Length); k++)                        
                                for (int l = Math.Max(0, j - numStr.Length); l < Math.Min(j + 2, input[i].Length); l++)                            
                                    if (!char.IsDigit(input[k][l]) && input[k][l] != '.')
                                    {
                                        sum += int.Parse(numStr);
                                        if (input[k][l] == '*')
                                        {
                                            if (starMap.ContainsKey((k, l)))                                        
                                                starMap[(k, l)].Add(int.Parse(numStr));                                        
                                            else
                                                starMap.Add((k,l),new List { int.Parse(numStr) });
                                        }
                                        goto endSymbSearch;
                                    }                           
                        endSymbSearch:
                            numStr = "";
                        }
                    }            
                Console.WriteLine("Result1:"+sum.ToString());
                Console.WriteLine("Result2:" + starMap.Where(sm => sm.Value.Count == 2).Sum(sm => sm.Value[0] * sm.Value[1]));
            }
        }
    
    
  • hades@lemm.ee
    link
    fedilink
    arrow-up
    3
    ·
    7 months ago

    Python

    Questions and comments welcome!

    import collections
    import re
    
    from .solver import Solver
    
    class Day03(Solver):
      def __init__(self):
        super().__init__(3)
        self.lines = []
    
      def presolve(self, input: str):
        self.lines = input.rstrip().split('\n')
    
      def solve_first_star(self):
        adjacent_to_symbols = set()
        for i, line in enumerate(self.lines):
          for j, sym in enumerate(line):
            if sym in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'):
              continue
            for di in (-1, 0, 1):
              for dj in (-1, 0, 1):
                adjacent_to_symbols.add((i + di, j + dj))
        numbers = []
        for i, line in enumerate(self. lines):
          for number_match in re.finditer(r'\d+', line):
            is_adjacent_to_symbol = False
            for j in range(number_match.start(), number_match.end()):
              if (i, j) in adjacent_to_symbols:
                is_adjacent_to_symbol = True
            if is_adjacent_to_symbol:
              numbers.append(int(number_match.group()))
        return sum(numbers)
    
      def solve_second_star(self):
        gear_numbers = collections.defaultdict(list)
        adjacent_to_gears = {}
        for i, line in enumerate(self.lines):
          for j, sym in enumerate(line):
            if sym == '*':
              for di in (-1, 0, 1):
                for dj in (-1, 0, 1):
                  adjacent_to_gears[(i + di, j + dj)] = (i, j)
        for i, line in enumerate(self. lines):
          for number_match in re.finditer(r'\d+', line):
            adjacent_to_gear = None
            for j in range(number_match.start(), number_match.end()):
              if (i, j) in adjacent_to_gears:
                adjacent_to_gear = adjacent_to_gears[(i, j)]
            if adjacent_to_gear:
              gear_numbers[adjacent_to_gear].append(int(number_match.group()))
        ratios = []
        for gear_numbers in gear_numbers.values():
          match gear_numbers:
            case [a, b]:
              ratios.append(a * b)
        return sum(ratios)
    
    
  • bugsmith@programming.dev
    link
    fedilink
    arrow-up
    3
    ·
    edit-2
    7 months ago

    Edit: Updated now with part 2.

    Managed to have a crack at this a bit earlier today, I’ve only done Part 01 so far. I’ll update with Part 02 later.

    I tackled this with the personal challenge of not loading the entire puzzle input into memory, which would have made this a bit easier.

    Solution in Rust 🦀

    View formatted on GitLab

    use std::{
        env, fs,
        io::{self, BufRead, BufReader, Read},
    };
    
    fn main() -> io::Result<()> {
        let args: Vec = env::args().collect();
        let filename = &args[1];
        let file1 = fs::File::open(filename)?;
        let file2 = fs::File::open(filename)?;
        let reader1 = BufReader::new(file1);
        let reader2 = BufReader::new(file2);
    
        println!("Part one: {}", process_part_one(reader1));
        println!("Part two: {}", process_part_two(reader2));
        Ok(())
    }
    
    fn process_part_one(reader: BufReader) -> u32 {
        let mut lines = reader.lines().peekable();
        let mut prev_line: Option = None;
        let mut sum = 0;
        while let Some(line) = lines.next() {
            let current_line = line.expect("line exists");
            let next_line = match lines.peek() {
                Some(Ok(line)) => Some(line),
                Some(Err(_)) => None,
                None => None,
            };
            match (prev_line, next_line) {
                (None, Some(next)) => {
                    let lines = vec![¤t_line, next];
                    sum += parse_lines(lines, true);
                }
                (Some(prev), Some(next)) => {
                    let lines = vec![&prev, ¤t_line, next];
                    sum += parse_lines(lines, false);
                }
                (Some(prev), None) => {
                    let lines = vec![&prev, ¤t_line];
                    sum += parse_lines(lines, false);
                }
                (None, None) => {}
            }
    
            prev_line = Some(current_line);
        }
        sum
    }
    
    fn process_part_two(reader: BufReader) -> u32 {
        let mut lines = reader.lines().peekable();
        let mut prev_line: Option = None;
        let mut sum = 0;
        while let Some(line) = lines.next() {
            let current_line = line.expect("line exists");
            let next_line = match lines.peek() {
                Some(Ok(line)) => Some(line),
                Some(Err(_)) => None,
                None => None,
            };
            match (prev_line, next_line) {
                (None, Some(next)) => {
                    let lines = vec![¤t_line, next];
                    sum += parse_lines_for_gears(lines, true);
                }
                (Some(prev), Some(next)) => {
                    let lines = vec![&prev, ¤t_line, next];
                    sum += parse_lines_for_gears(lines, false);
                }
                (Some(prev), None) => {
                    let lines = vec![&prev, ¤t_line];
                    sum += parse_lines_for_gears(lines, false);
                }
                (None, None) => {}
            }
    
            prev_line = Some(current_line);
        }
    
        sum
    }
    
    fn parse_lines(lines: Vec<&String>, first_line: bool) -> u32 {
        let mut sum = 0;
        let mut num = 0;
        let mut valid = false;
        let mut char_vec: Vec> = Vec::new();
        for line in lines {
            char_vec.push(line.chars().collect());
        }
        let chars = match first_line {
            true => &char_vec[0],
            false => &char_vec[1],
        };
        for i in 0..chars.len() {
            if chars[i].is_digit(10) {
                // Add the digit to the number
                num = num * 10 + chars[i].to_digit(10).expect("is digit");
    
                // Check the surrounding character for non-period symbols
                for &x in &[-1, 0, 1] {
                    for chars in &char_vec {
                        if (i as isize + x).is_positive() && ((i as isize + x) as usize) < chars.len() {
                            let index = (i as isize + x) as usize;
                            if !chars[index].is_digit(10) && chars[index] != '.' {
                                valid = true;
                            }
                        }
                    }
                }
            } else {
                if valid {
                    sum += num;
                }
                valid = false;
                num = 0;
            }
        }
        if valid {
            sum += num;
        }
        sum
    }
    
    fn parse_lines_for_gears(lines: Vec<&String>, first_line: bool) -> u32 {
        let mut sum = 0;
        let mut char_vec: Vec> = Vec::new();
        for line in &lines {
            char_vec.push(line.chars().collect());
        }
        let chars = match first_line {
            true => &char_vec[0],
            false => &char_vec[1],
        };
        for i in 0..chars.len() {
            if chars[i] == '*' {
                let surrounding_nums = get_surrounding_numbers(&lines, i);
                let product = match surrounding_nums.len() {
                    0 | 1 => 0,
                    _ => surrounding_nums.iter().product(),
                };
                sum += product;
            }
        }
        sum
    }
    
    fn get_surrounding_numbers(lines: &Vec<&String>, gear_pos: usize) -> Vec {
        let mut nums: Vec = Vec::new();
        let mut num: u32 = 0;
        let mut valid = false;
        for line in lines {
            for (i, char) in line.chars().enumerate() {
                if char.is_digit(10) {
                    num = num * 10 + char.to_digit(10).expect("is digit");
                    if [gear_pos - 1, gear_pos, gear_pos + 1].contains(&i) {
                        valid = true;
                    }
                } else if num > 0 && valid {
                    nums.push(num);
                    num = 0;
                    valid = false;
                } else {
                    num = 0;
                    valid = false;
                }
            }
            if num > 0 && valid {
                nums.push(num);
            }
            num = 0;
            valid = false;
        }
        nums
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        const INPUT: &str = "467..114..
    ...*......
    ..35..633.
    ......#...
    617*......
    .....+.58.
    ..592.....
    ......755.
    ...$.*....
    .664.598..";
    
        #[test]
        fn test_process_part_one() {
            let input_bytes = INPUT.as_bytes();
            assert_eq!(4361, process_part_one(BufReader::new(input_bytes)));
        }
    
        #[test]
        fn test_process_part_two() {
            let input_bytes = INPUT.as_bytes();
            assert_eq!(467835, process_part_two(BufReader::new(input_bytes)));
        }
    }
    
  • StreetKid@reddthat.com
    link
    fedilink
    English
    arrow-up
    3
    ·
    7 months ago

    My Python solution for part 1 and part 2. I really practice my regex skills.

    spoiler
    #!/usr/bin/python3
    
    import re
    
    value_re = '(\d+)'
    symbol_re = '[^\d.]'
    gear_re = '(\*)'
    
    def main():
        input = list()
        with open("input.txt", 'r') as in_file:
            for line in in_file:
                input.append(line.strip('\n'))
        length = len(input)
        width = len(input[0])
        value_sum = 0
        for idx, line in enumerate(input):
            for match in re.finditer(value_re, line):
                for line_mask in input[max(idx - 1, 0):min(idx + 2, length)]:
                    valid_chars = line_mask[max(match.span()[0] - 1, 0):min(match.span()[1] + 1, width)]
                    if re.search(symbol_re, valid_chars):
                        value_sum += int(match[0])
                        break
        print(f"Value sum = {value_sum}")
    
        gear_ratio = 0
        for idx, line in enumerate(input):
            for match in re.finditer(gear_re, line):
                valid_lines = input[max(idx - 1, 0):min(idx + 2, length)]
                min_range = max(match.span()[0] - 1, 0)
                max_range = min(match.span()[1], width)
                num_of_adjacent = 0
                temp_gear_ratio = 1
                for valid_line in valid_lines:
                    for match in re.finditer(value_re, valid_line):
                        if match.span()[0] in range(min_range,max_range + 1) or match.span()[1] - 1 in range(min_range,max_range + 1):
                            num_of_adjacent += 1
                            temp_gear_ratio *= int(match[0])
                if num_of_adjacent == 2:
                    gear_ratio += temp_gear_ratio
        print(f"Gear ratio = {gear_ratio}")
    
    if __name__ == '__main__':
        main()
    
  • RowanCH@lemmy.blahaj.zone
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    7 months ago

    Language: C++

    Efficiency? Elegant Code? Nope but It works. Luckily I did part 1 by looking at the symbols first anyway, so extending to part two was trivial. Also originally had a bug where I treated all symbols as cogs, not only ‘*’. Interestingly it worked anyway as only '*'s had two adjacent numbers in my data. It is fixed in this version. Hacked together combined code (originally I did each part as separate programs but they shared so much that I ended up combining then so the post is shorter): https://pastebin.com/Dij2XSYe

    Edit: anything in angle brackets is not displaying even with backslashes, idk why but i have moved the code to a pastebin.

  • Ategon@programming.devOPM
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    7 months ago

    [Rust] Harder one today, for part 1 I ended up getting stuck for a bit since I wasnt taking numbers at the end of lines into account and in part 2 I defined my gears vector in the wrong spot and spent a bit debugging that

    Code

    (lemmy removes some chars, all chars are in code link)

    use std::fs;
    
    fn part1(input: String) -> u32 {
        let lines = input.lines().collect::>();
        let mut sum = 0;
    
        for i in 0..lines.len() {
            let mut num = 0;
            let mut valid = false;
            let chars = lines[i].chars().collect::>();
    
            for j in 0..chars.len() {
                let character = chars[j];
                let parts = ['*', '#', '+', '$', '/', '%', '=', '-', '&', '@'];
    
                if character.is_digit(10) {
                    num = num * 10 + character.to_digit(10).unwrap();
    
                    if i > 0 {
                        if parts.contains(&lines[i - 1].chars().collect::>()[j]) {
                            valid = true;
                        }
    
                        if j > 0 {
                            if parts.contains(&lines[i - 1].chars().collect::>()[j - 1]) {
                                valid = true;
                            }
                        }
    
                        if j < chars.len() - 1 {
                            if parts.contains(&lines[i - 1].chars().collect::>()[j + 1]) {
                                valid = true;
                            }
                        }
                    }
    
                    if i < lines.len() - 1 {
                        if parts.contains(&lines[i + 1].chars().collect::>()[j]) {
                            valid = true;
                        }
    
                        if j > 0 {
                            if parts.contains(&lines[i + 1].chars().collect::>()[j - 1]) {
                                valid = true;
                            }
                        }
    
                        if j < chars.len() - 1 {
                            if parts.contains(&lines[i + 1].chars().collect::>()[j + 1]) {
                                valid = true;
                            }
                        }
                    }
    
                    if j > 0 {
                        if parts.contains(&lines[i].chars().collect::>()[j - 1]) {
                            valid = true;
                        }
                    }
    
                    if j < chars.len() - 1 {
                        if parts.contains(&lines[i].chars().collect::>()[j + 1]) {
                            valid = true;
                        }
                    }
                }
                else {
                    if valid == true {
                        sum += num;
                    }
    
                    num = 0;
                    valid = false;
                }
    
                if j == chars.len() - 1 {
                    if valid == true {
                        sum += num;
                    }
    
                    num = 0;
                    valid = false;
                }
            }
        }
    
        return sum;
    }
    
    fn part2(input: String) -> u32 {
        let lines = input.lines().collect::>();
        let mut gears: Vec<(usize, usize, u32)> = Vec::new();
        let mut sum = 0;
    
        for i in 0..lines.len() {
            let mut num = 0;
            let chars = lines[i].chars().collect::>();
            let mut pos: (usize, usize) = (0, 0);
            let mut valid = false;
    
            for j in 0..chars.len() {
                let character = chars[j];
                let parts = ['*'];
    
                if character.is_digit(10) {
                    num = num * 10 + character.to_digit(10).unwrap();
    
                    if i > 0 {
                        if parts.contains(&lines[i - 1].chars().collect::>()[j]) {
                            valid = true;
                            pos = (i - 1, j);
                        }
    
                        if j > 0 {
                            if parts.contains(&lines[i - 1].chars().collect::>()[j - 1]) {
                                valid = true;
                                pos = (i - 1, j - 1);
                            }
                        }
    
                        if j < chars.len() - 1 {
                            if parts.contains(&lines[i - 1].chars().collect::>()[j + 1]) {
                                valid = true;
                                pos = (i - 1, j + 1);
                            }
                        }
                    }
    
                    if i < lines.len() - 1 {
                        if parts.contains(&lines[i + 1].chars().collect::>()[j]) {
                            valid = true;
                            pos = (i + 1, j);
                        }
    
                        if j > 0 {
                            if parts.contains(&lines[i + 1].chars().collect::>()[j - 1]) {
                                valid = true;
                                pos = (i + 1, j - 1);
                            }
                        }
    
                        if j < chars.len() - 1 {
                            if parts.contains(&lines[i + 1].chars().collect::>()[j + 1]) {
                                valid = true;
                                pos = (i + 1, j + 1);
                            }
                        }
                    }
    
                    if j > 0 {
                        if parts.contains(&lines[i].chars().collect::>()[j - 1]) {
                            valid = true;
                            pos = (i, j - 1);
                        }
                    }
    
                    if j < chars.len() - 1 {
                        if parts.contains(&lines[i].chars().collect::>()[j + 1]) {
                            valid = true;
                            pos = (i, j + 1);
                        }
                    }
                }
                else {
                    if valid == true {
                        let mut current_gear = false;
                        
                        for gear in &gears {
                            if gear.0 == pos.0 && gear.1 == pos.1 {
                                sum += num * gear.2;
                                current_gear = true;
                                break;
                            }
                        }
                        
                        if !current_gear {
                            let tuple_to_push = (pos.0.clone(), pos.1.clone(), num.clone());
                            gears.push((pos.0.clone(), pos.1.clone(), num.clone()));
                        }
                    }
    
                    num = 0;
                    valid = false;
                }
    
                if j == chars.len() - 1 {
                    if valid == true {
                        let mut current_gear = false;
                        
                        for gear in &gears {
                            if gear.0 == pos.0 && gear.1 == pos.1 {
                                sum += num * gear.2;
                                current_gear = true;
                                break;
                            }
                        }
                        
                        if !current_gear {
                            let tuple_to_push = (pos.0.clone(), pos.1.clone(), num.clone());
                            gears.push((pos.0.clone(), pos.1.clone(), num.clone()));
                        }
                    }
    
                    num = 0;
                    valid = false;
                }
            }
        }
    
        return sum;
    }
    
    fn main() {
        let input = fs::read_to_string("data/input.txt").unwrap();
    
        println!("{}", part1(input.clone()));
        println!("{}", part2(input.clone()));
    }
    

    Code Link

  • Ananace@lemmy.ananace.dev
    link
    fedilink
    arrow-up
    2
    ·
    7 months ago

    I get the feeling that I should include some default types for handling 2D maps in my boilerplate, it’s a very recurring problem in AoC after all.

    My solution is reasonably simplistic - and therefore also a bit slow, but the design meant I could do part 2 with just a few extra lines of code on the already processed data, here’s the functional part of it; (I push the previous days solution as part of my workflow for starting with the current day so the full code won’t be up until tomorrow)

    Ruby

    The code has been compressed for brevity.

    Point = Struct.new('Point', :x, :y)
    PartNumber = Struct.new('PartNumber', :number, :adjacent) do
      def adjacent?(to); adjacent.include?(to); end
      def irrelevant?; adjacent.empty?; end
      def to_i; number; end
    end
    
    class Implementation
      def initialize
        @map = []; @dim = { width: 0, height: 0 }; @symbols = []; @numbers = []
      end
    
      def input(line)
        @dim[:width] = line.size; @dim[:height] += 1
        @map += line.chars
      end
    
      def calc
        for y in (0..@dim[:height]-1) do
          for x in (0..@dim[:width]-1) do
            chr = get(x, y); next if chr =~ /\d/ || chr == '.'
            @symbols << Point.new(x, y)
          end
        end
    
        for y in (0..@dim[:height]-1) do
          buf = ""; adj = []
          for x in (0..@dim[:width]) do # Going one over, to fake a non-number as an end char on all lines
            chr = get(x, y)
            if chr =~ /\d/
              buf += chr
              (-1..1).each do |adj_x|
                (-1..1).each do |adj_y|
                  next if adj_x == 0 && adj_y == 0 ||
                    (x + adj_x < 0) || (x + adj_x >= @dim[:width]) ||
                    (y + adj_y < 0) || (y + adj_y >= @dim[:height])
                  sym = Point.new(x + adj_x, y + adj_y)
                  adj << sym if @symbols.any? sym
                end
              end
            elsif !buf.empty?
              @numbers << PartNumber.new(buf.to_i, adj)
              buf = ""; adj = []
            end
          end
        end
      end
    
      def output
        part1 = @numbers.reject(&:irrelevant?).map(&:to_i).sum
        puts "Part 1:", part1
    
        gears = @symbols.select do |sym|
          next unless get(sym) == '*'
          next unless @numbers.select { |num| num.adjacent? sym }.size == 2
          true
        end
        part2 = gears.sum { |gear| @numbers.select { |num| num.adjacent? gear }.map(&:to_i).inject(:*) }
    
        puts "Part 2:", part2
      end
    
      private
    
      def get(x, y = -1)
        y = x.y if x.is_a?(Point)
        x = x.x if x.is_a?(Point)
        return unless (0..@dim[:width]-1).include?(x) && (0..@dim[:height]-1).include?(y)
    
        @map[y * @dim[:width] + x % @dim[:width]]
      end
    end