16 votes

Day 2: Red-Nosed Reports

Today's problem description: https://adventofcode.com/2024/day/2

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

20 comments

  1. csos95
    (edited )
    Link
    Today's took me a bit longer for the second part because I misunderstood the change and for some reason thought if there was a single bad level I could simply remove it without checking if the new...

    Today's took me a bit longer for the second part because I misunderstood the change and for some reason thought if there was a single bad level I could simply remove it without checking if the new report is safe.

    Once I realized my mistake it was pretty straightforward.

    My only real annoyance with Rhai so far is that there's no good mode for emacs for it.
    I found an old one someone made, but the way it handles the indentations is really janky.
    It seems like it's sort of trying to line the indentation up with the first letter of the second token on the line above and if that fails, it just does a massive amount of indentation.
    So I've got anywhere from two to four spaces of indentation depending on the line.

    Rhai Code
    fn get_input(day, example) {
       if example {
          let input = open_file(`day${day}_example.txt`)
          	  .read_string();
          input.trim();
          input
       } else {
          let input = open_file(`day${day}.txt`)
              .read_string();
          input.trim();
          input
       }
    }
    
    // INPUT
    let input = get_input(2, false);
    let reports = [];
    for line in input.split("\n") {
        let report = [];
        for level in line.split() {
        	report.push(level.parse_int());
        }
        reports.push(report);
    }
    
    // AUXILIARY
    fn is_decreasing(report) {
       if report.len() < 2 {
          true
       } else {
          report[0] > report[1] && is_decreasing(report.extract(1))
       }
    }
    
    fn is_increasing(report) {
       if report.len() < 2 {
          true
       } else {
          report[0] < report[1] && is_increasing(report.extract(1))
       }
    }
    
    fn is_gradual(report) {
       if report.len() < 2 {
          true
       } else {
          let diff = (report[0] - report[1]).abs();
          diff > 0 && diff < 4 && is_gradual(report.extract(1))
       }
    }
    
    // PART 1
    let safe = 0;
    for report in reports {
        if (is_decreasing(report) || is_increasing(report)) && is_gradual(report) {
           safe += 1;
        }
    }
    
    print(`part 1: ${safe}`);
    
    // PART 2
    let safe = 0;
    
    for report in reports {
        if (is_decreasing(report) || is_increasing(report)) && is_gradual(report) {
           safe += 1;
        } else {
           for i in 0..report.len() {
              let report_copy = report;
    	  report_copy.remove(i);
              if (is_decreasing(report_copy) || is_increasing(report_copy)) && is_gradual(report_copy) {
                 safe += 1;
    	     break;
    	  }
           }
        }
    }
    
    print(`part 2: ${safe}`);
    

    Edit: I made it more compact and moved the get_input function to a separate file since it'll be used every day.

    Rhai Code
    import "utils" as utils;
    
    // INPUT
    let reports = utils::get_input(2, false)
       .split("\n")
       .map(|report| report
       		    .split()
    		    .map(|l| l.parse_int()));
    
    // AUXILIARY
    fn is_safe_inner(report, last_diff) {
       if report.len() < 2 {
          return true;
       }
       let diff = report[0] - report[1];
       let diff_abs = diff.abs();
       if last_diff != () && last_diff * diff < 0 {
          return false;
       }
       diff_abs > 0 && diff_abs < 4 && is_safe_inner(report.extract(1), diff)
    }
    
    fn is_safe(report) {
       is_safe_inner(report, ())
    }
    
    // PART 1
    let safe = reports.filter(is_safe).len();
    
    print(`part 1: ${safe}`);
    
    // PART 2
    let safe = reports.filter(|report| {
        if is_safe(report) {
           return true;
        } else {
           for i in 0..report.len() {
              let report = report;
    	  report.remove(i);
              if is_safe(report) {
    	     return true;
    	  }
           }
        }
        false
    }).len();
    
    print(`part 2: ${safe}`);
    
    4 votes
  2. lily
    Link
    There's probably a smarter way to do part 2... but I couldn't be bothered to figure it out. The brute-force method was good enough for me. Incidentally, there should really be a sign()...

    There's probably a smarter way to do part 2... but I couldn't be bothered to figure it out. The brute-force method was good enough for me.

    Incidentally, there should really be a sign() implementation in the Math module. Not that it's hard to write one myself - it just feels like an odd gap in the standard library.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 02: Red-Nosed Reports
     */
    
    #import "Basic";
    #import "File";
    #import "Math";
    #import "String";
    
    sign :: (n: int) -> int {
        if n > 0 return 1;
        if n < 0 return -1;
        return 0;
    }
    
    is_report_safe :: (report: [] int) -> bool {
        difference_sign := 0;
        for 1..report.count - 1 {
            difference := report[it] - report[it - 1];
    
            if difference_sign == 0 {
                difference_sign = sign(difference);
            } else if sign(difference) != difference_sign {
                return false;
            }
    
            if abs(difference) < 1 || abs(difference) > 3 {
                return false;
            }
        }
    
        return true;
    }
    
    main :: () {
        input, success := read_entire_file("inputs/day_02.txt");
        assert(success);
    
        reports: [..][..] int;
        for split(input, "\n") {
            if it == "" {
                continue;
            }
    
            report: [..] int;
            for split(it, " ") {
                array_add(*report, string_to_int(it));
            }
    
            array_add(*reports, report);
        }
    
        safe_reports := 0;
        mostly_safe_reports := 0;
    
        for report: reports {
            if is_report_safe(report) {
                safe_reports += 1;
                mostly_safe_reports += 1;
            } else {
                for 0..report.count - 1 {
                    modified_report := array_copy(report);
                    array_ordered_remove_by_index(*modified_report, it);
    
                    if is_report_safe(modified_report) {
                        mostly_safe_reports += 1;
                        break;
                    }
                }
            }
        }
    
        print("Part 1: %\nPart 2: %\n", safe_reports, mostly_safe_reports);
    }
    
    4 votes
  3. [3]
    jonah
    Link
    Now that I'm off work I can post my solutions: Part 1 from common import load_input input = load_input() def get_direction(first, second): return 1 if second - first > 0 else -1 def...

    Now that I'm off work I can post my solutions:

    Part 1
    from common import load_input
    input = load_input()
    
    def get_direction(first, second):
        return 1 if second - first > 0 else -1
    
    def validate_report(levels):
        dir = get_direction(levels[0], levels[1])
        for i in range(0, len(levels) - 1):
            diff = levels[i + 1] - levels[i]
            adiff = abs(diff)
            if adiff > 3 or adiff < 1 or get_direction(levels[i], levels[i + 1]) != dir:
                return False
        return True
    
    score = 0
    reports = input.split("\n")
    for report in reports:
        levels = list(map(lambda x: int(x), report.split(" ")))
        if validate_report(levels):
            score += 1
        else:
            for i in range(0, len(levels)):
                copy = levels.copy()
                copy.pop(i)
                if validate_report(copy):
                    score += 1
                    break
    
    print(score)
    
    Part 2
    from common import load_input
    input = load_input()
    
    def get_direction(first, second):
        return 1 if second - first > 0 else -1
    
    def validate_report(levels):
        dir = get_direction(levels[0], levels[1])
        for i in range(0, len(levels) - 1):
            diff = levels[i + 1] - levels[i]
            adiff = abs(diff)
            if adiff > 3 or adiff < 1 or get_direction(levels[i], levels[i + 1]) != dir:
                return False
        return True
    
    score = 0
    reports = input.split("\n")
    for report in reports:
        levels = list(map(lambda x: int(x), report.split(" ")))
        if validate_report(levels):
            score += 1
        else:
            for i in range(0, len(levels)):
                copy = levels.copy()
                copy.pop(i)
                if validate_report(copy):
                    score += 1
                    break
    
    print(score)
    

    Part 2 took me a while because I was trying to be cute. I gave up and just brute forced it which seemed to work well enough. I'm still trying to get used to Python. For those who know, is there a cleaner way for me to convert my list of strings to a list of ints? I'm still stuck in functional Javascript world and want to use maps everywhere, but maybe there's an easier or more idiomatic way to do it.

    4 votes
    1. [2]
      scarecrw
      Link Parent
      I don't know if there's that much of an easier way, but the main idiomatic difference would be using list comprehension instead of mapping. Here's a one-liner if you want which would leave you...

      I don't know if there's that much of an easier way, but the main idiomatic difference would be using list comprehension instead of mapping.

      Here's a one-liner if you want which would leave you with a list of lists of ints:

      reports = [[int(x) for x in row.split()] for row in input.split('\n')]
      

      You can also use map, as you've done, though I'll note you can actually just pass int as a function, rather than writing a lambda:

      levels = list(map(int, report.split(' '))
      

      Last little idiomatic tip, you probably want to avoid using input as a variable name, as it overshadows the built-in function of the same name.

      3 votes
      1. jonah
        Link Parent
        Wonderful, thank you very much!

        Wonderful, thank you very much!

        1 vote
  4. scarecrw
    (edited )
    Link
    So far keeping up OOP and TDD! This took... far longer than it should have, but I'm definitely learning more about Smalltalk. Discovering overlappingPairsCollect and copyWithoutIndex handled a lot...

    So far keeping up OOP and TDD! This took... far longer than it should have, but I'm definitely learning more about Smalltalk. Discovering overlappingPairsCollect and copyWithoutIndex handled a lot of the heavy lifting here. Also, Pharo shows little green dots when the tests pass, and getting them all to light up is a wonderful way to tie problem completion to dopamine release.

    I think I'll have to swap to just posting a github link and snippets going forward. Getting to a somewhat reasonable length means cutting out the comments, protocols, accessors, tests...

    Smalltalk Solution
    Class {
    	#name : 'Day2Solver',
    	#superclass : 'AoCSolver',
    	#instVars : [
    		'reports'
    	],
    	#category : 'AoCDay2',
    	#package : 'AoCDay2'
    }
    
    Day2Solver >> parseRawData [
    	reports := rawData collect: [ :s | Day2Report newFromString: s].
    ]
    
    Day2Solver >> solvePart1 [
    	^ reports count: [ :report | report isSafe ]
    ]
    
    Day2Solver >> solvePart2 [
    	^ reports count: [ :report | report isNearlySafe ]
    ]
    
    Class {
    	#name : 'Day2Report',
    	#superclass : 'OrderedCollection',
    	#category : 'AoCDay2',
    	#package : 'AoCDay2'
    }
    
    Day2Report class >> newFromString: aString [ 
    	^ self newFrom: ((aString findTokens: ' ') collect: [ :x | x asNumber ])
    ]
    
    Day2Report >> isDecreasing [
    	^ (self overlappingPairsCollect: [ :a :b | a > b ]) allSatisfy: [ :x | x ].
    ]
    
    Day2Report >> isIncreasing [
    	^ (self overlappingPairsCollect: [ :a :b | a < b ]) allSatisfy: [ :x | x ].
    ]
    
    Day2Report >> isNearlySafe [
    	^ (1 to: self size) anySatisfy: [ :idx | (self copyWithoutIndex: idx) isSafe]
    ]
    
    Day2Report >> isSafe [
    	^ (self isIncreasing or: self isDecreasing) and: self levelsAreSimilar
    ]
    
    Day2Report >> levelsAreSimilar [
    	^ (self overlappingPairsCollect: [ :a :b | (a - b) abs between: 1 and: 3]) allSatisfy: [ :x | x ].
    ]
    
    3 votes
  5. [4]
    DataWraith
    (edited )
    Link
    I'm using Rust again this year. I considered trying out Julia, but I don't want to be fighting both the puzzles and the language. Code The code is a bit inefficient, but with sub-millisecond...

    I'm using Rust again this year.
    I considered trying out Julia, but I don't want to be fighting both the puzzles and the language.

    Code

    The code is a bit inefficient, but with sub-millisecond runtimes, I'm not sweating that.

    Part 1

    // Uninteresting parts omitted
    
    pub fn part1(input: &PuzzleInput) -> String {
        input
            .reports
            .iter()
            .filter(|r| is_safe(r))
            .count()
            .to_string()
    }
    
    pub fn is_safe(report: &Vec<i64>) -> bool {
        (is_increasing(report) || is_decreasing(report)) && abs_diff_correct(report)
    }
    
    fn is_increasing(report: &Vec<i64>) -> bool {
        report.windows(2).all(|w| w[0] < w[1])
    }
    
    fn is_decreasing(report: &Vec<i64>) -> bool {
        report.windows(2).all(|w| w[0] > w[1])
    }
    
    fn abs_diff_correct(report: &Vec<i64>) -> bool {
        report
            .windows(2)
            .all(|w| w[0].abs_diff(w[1]) >= 1 && w[0].abs_diff(w[1]) <= 3)
    }
    

    Edit: After some more refactoring:

    pub fn is_safe(report: &Vec<i64>) -> bool {
        report.windows(2).all(|w| (1..=3).contains(&(w[0] - w[1])))
            || report.windows(2).all(|w| (1..=3).contains(&(w[1] - w[0])))
    }
    

    Part 2

    Solved in the most-naive way possible -- by just deleting each number in turn
    and checking if that makes the report safe.

    3 votes
    1. Halfloaf
      Link Parent
      Julia has been really tempting to me as well - I come from a MATLAB background, and julia does have a really nice Control System library available.

      Julia has been really tempting to me as well - I come from a MATLAB background, and julia does have a really nice Control System library available.

      2 votes
    2. [2]
      kari
      Link Parent
      Me right now trying to learn Racket, but it's fun :P

      but I don't want to be fighting both the puzzles and the language.

      Me right now trying to learn Racket, but it's fun :P

      1 vote
      1. DataWraith
        Link Parent
        You could be regretting your choices come day 18 or so, though. ;-)

        You could be regretting your choices come day 18 or so, though. ;-)

        1 vote
  6. [3]
    kari
    Link
    Took me a while ago but I think I'm sorta kinda getting the hang of some basic Racket programming. I spent a while trying to figure out smarter ways for both parts but even as it is, running both...

    Took me a while ago but I think I'm sorta kinda getting the hang of some basic Racket programming. I spent a while trying to figure out smarter ways for both parts but even as it is, running both (and printing) only takes 0.225s on my machine.

    Racket solution
    #! /usr/bin/env racket
    #lang racket
    
    (require racket/base rackunit)
    
    (define (remove-element-by-index lst i)
      (if (null? lst)
          empty
          (if (= i 0)
              (cdr lst)
              (cons (car lst) (remove-element-by-index (cdr lst) (- i 1))))))
    
    ; split each report into its levels and convert from string to number
    (define (reports in)
      (for/list/concurrent ([l (in-lines in)])
        (map string->number (string-split l " "))))
    
    ; reports are safe if-and-only-if:
    ;  - The levels are either all increasing or all decreasing.
    ;  - Any two adjacent levels differ by at least one and at most three.
    (define (safe? report)
      (and (or (for/and ([x (in-list report)]
                         [y (in-list (cdr report))])
                 (< x y))
               (for/and ([x (in-list report)]
                         [y (in-list (cdr report))])
                 (> x y)))
           (for/and ([x (in-list report)]
                     [y (in-list (cdr report))])
             (let ([difference (abs (- x y))])
               (and (>= difference 1)
                    (<= difference 3))))))
    
    ; reports are safe if, when removing AT MOST 1 bad level, the remaining:
    ;  - The levels are either all increasing or all decreasing.
    ;  - Any two adjacent levels differ by at least one and at most three.
    ; this procedure just brute-forces it...
    (define (tolerant-safe? report)
      (or (safe? report)
          (for/or ([i (in-range (length report))])
            (safe? (remove-element-by-index report i)))))
    
    
    (define (run-part01 in)
      (foldl (lambda (cur result)
               (if cur
                   (+ 1 result)
                   result))
             0
             (for/list ([report (reports in)])
               (safe? report))))
    
    (define (run-part02 in)
      (foldl (lambda (cur result)
               (if cur
                   (+ 1 result)
                   result))
             0
             (for/list ([report (reports in)])
               (or (safe? report) (tolerant-safe? report)))))
    
    (let ([input (open-input-file "inputs/day02.in")])
      (printf "~a\n" (run-part01 input)))
    (let ([input (open-input-file "inputs/day02.in")])
      (printf "~a\n" (run-part02 input)))
    
    (check-equal? (run-part01 (open-input-file "examples/day02.in")) 2 "Test part 1")
    (check-equal? (run-part02 (open-input-file "examples/day02.in")) 4 "Test part 2")
    

    Once again, anyone who knows Racket can feel free to tell me what I did that not idiomatic (or just what's inefficient, too).

    3 votes
    1. [2]
      bhrgunatha
      Link Parent
      Comment 1 You've specified #lang racket and used (require racket/base rackunit) - racket/base is redundant since it's a subset of #lang racket Comment 2 Since tolerant-safe? already calls safe? I...
      Comment 1

      You've specified #lang racket and used (require racket/base rackunit) - racket/base is redundant since it's a subset of #lang racket

      Comment 2

      Since tolerant-safe? already calls safe? I think (or (safe? report) (tolerant-safe? report)) cab be (tolerant-safe? report). That way you could replace the fold's in both parts with count ah.. ah.. aaaah

      Comment 3

      I was unaware of for/list/concurrent so thanks for introducing me to that! Does it buy you anything reading from a port though. Isn't the port a shared resource between all the threads? I'll have to experiment myself with that.

      2 votes
      1. kari
        Link Parent
        Thanks!! For comment 3, no clue, I just saw it when I was searching the racket docs and tried it 😂 I should probably spend some time going through the rest of the guide, I basically saw ports are...

        Thanks!! For comment 3, no clue, I just saw it when I was searching the racket docs and tried it 😂

        I should probably spend some time going through the rest of the guide, I basically saw ports are how to do I/O and then used it, that was it. :P

  7. xavdid
    Link
    Step-by-step explanation | full code My key today was realizing a strictly increasing list is the same as a strictly decreasing one, just reversed. Otherwise, pretty straightforward today thanks...

    Step-by-step explanation | full code

    My key today was realizing a strictly increasing list is the same as a strictly decreasing one, just reversed. Otherwise, pretty straightforward today thanks to Python's any and all functions! Nice way to practice writing pure helper functions for is_safe and is_strictly_increasing.

    3 votes
  8. minion
    Link
    Back again writing lisp! The code for today is definitely worse than on day 1, and I spent a silly amount of time trying to over engineer a problem dampener to blow a fuse at the first sign of an...

    Back again writing lisp!

    The code for today is definitely worse than on day 1, and I spent a silly amount of time trying to over engineer a problem dampener to blow a fuse at the first sign of an error rather than doing the naive solution.

    That said, I spent a lot less time tripping over the language today, although I did get confused about some finer points of loops and appending to lists

    My code
    (require 'uiop)
    
    (defun contentful-file-lines (filename)
      (let* (
    	(content (uiop:read-file-lines filename))
    	(lines-without-empties
    	  (remove-if
    	    (lambda (str) (string= "" str))
    	    content)))
        lines-without-empties))
    
    (defun space-split (str)
      (uiop:split-string str :separator " "))
    
    (defun to-integers (strings)
      (mapcar #'parse-integer strings))
    
    (defun lists-to-integers (lists)
      (mapcar #'to-integers lists))
    
    (defun reports (filename)
      (let* (
    	 (lines (contentful-file-lines filename))
    	 (digits (mapcar #'space-split lines))
    	 (reports (lists-to-integers digits)))
        reports))
    
    (defun difference-safe (cur prev increasing)
        (list
          (and
    	(and
    	  (> (abs (- prev cur)) 0)
    	  (< (abs (- prev cur)) 4))
    	(equal increasing (> 0 (- cur prev))))
          cur
          increasing))
    
    ; I tried something clever here to automatically do the problem damper
    ; I faced trouble with removing the first item and also changing the direction of the list after removing the second item
    (defun safe (report)
      (first
        (reduce
          (lambda (prev digit)
            (if (first prev)
    	  (difference-safe digit (second prev) (third prev))
    	  (list nil)))
          (cdr report)
          :initial-value (list
    		       t
    		       (first report)
    		       (> 0 (- (second report) (first report)))))))
    
    (defun part1 (filename)
      (let* (
    	 (reports (reports filename))
    	 (report-safeties (mapcar
    			    (lambda (report) (safe report))
    			    reports))
    	 (total (reduce
    		  (lambda (c safe)
    		    (+ c (if safe 1 0)))
    		  report-safeties
    		  :initial-value 0)))
        total))
    
    (defun any (booleans)
      (reduce (lambda (i1 i2) (or i1 i2)) booleans :initial-value nil))
    
    (defun sublists (items)
      (let (
    	(before '()))
        (loop :for i :on items
    	  :collect (concatenate 'list (reverse before) (cdr i))
    	  :do (push (first i) before))))
    
    (defun safe-with-dampener (report)
      (any
        (mapcar #'safe (append (list report) (sublists report)))))
    
    (defun part2 (filename)
      (let* (
    	 (reports (reports filename))
    	 (report-safeties (mapcar
    			    (lambda (report) (safe-with-dampener report))
    			    reports))
    	 (total (reduce
    		  (lambda (c safe)
    		    (+ c (if safe 1 0)))
    		  report-safeties
    		  :initial-value 0)))
        total))
    
    3 votes
  9. jzimbel
    Link
    Elixir I thought for a while about what a "smart" approach for part 2 would look like, but then I tried out the brute-force approach since it was a one-liner, and it ran in a few hundred...

    Elixir

    I thought for a while about what a "smart" approach for part 2 would look like, but then I tried out the brute-force approach since it was a one-liner, and it ran in a few hundred microseconds so... seems good!

    Both parts

    In safe_p1?, the Stream.scan call lazily checks each pair of numbers, emitting :not_safe if any pair is invalid. (The other values it emits aren't important as long as they are something other than :not_safe.)

    The Enum.any? call stops and returns false as soon as it finds a :not_safe in the stream.

    This combo is (IMO) a bit cleaner and easier to understand than a single Enum.reduce_while that achieves the same "stateful short-circuiting check" logic.


    I use a boolean accumulator, asc?, to track whether the numbers are ascending (true) or descending (false). It starts out as nil and gets set after the first interval is checked.

    defmodule AdventOfCode.Solution.Year2024.Day02 do
      use AdventOfCode.Solution.SharedParse
    
      def parse(input) do
        for line <- String.split(input, "\n", trim: true) do
          line |> String.split() |> Enum.map(&String.to_integer/1)
        end
      end
    
      def part1(reports), do: Enum.count(reports, &safe_p1?/1)
      def part2(reports), do: Enum.count(reports, &safe_p2?/1)
    
      defp safe_p1?(ns) do
        ns
        |> Enum.chunk_every(2, 1, :discard)
        |> Stream.scan(nil, &check_interval/2)
        |> Enum.all?(&(&1 != :not_safe))
      end
    
      defp check_interval([a, b], asc?) do
        with true <- gradual?(a, b),
             {true, asc?} <- asc_desc?(a, b, asc?) do
          asc?
        else
          false -> :not_safe
        end
      end
    
      defp gradual?(a, b), do: abs(b - a) in 1..3
    
      defp asc_desc?(a, b, nil), do: {true, b - a > 0}
      defp asc_desc?(a, b, asc?) when b - a > 0 == asc?, do: {true, asc?}
      defp asc_desc?(_a, _b, _asc?), do: false
    
      # Brute-force runs in a few hundred µs so I guess it's fine!
      defp safe_p2?(ns) do
        safe_p1?(ns) or
          Enum.any?(
            0..(length(ns) - 1),
            &safe_p1?(List.delete_at(ns, &1))
          )
      end
    end
    
    3 votes
  10. Halfloaf
    Link
    I'm back with a Python solution! Day 2 was a little trickier than day 1, but not horribly! I did go a little over the top today to give it a command line interface, but it was a fun bit of...

    I'm back with a Python solution!

    Day 2 was a little trickier than day 1, but not horribly!

    I did go a little over the top today to give it a command line interface, but it was a fun bit of practice. I also did it purely with vim, which was a fun departure from my normal vscode.

    My partner is an excel whiz, and she's been playing around with them too. She's always been tempted to learn Python, but she's been a little intimidated with it so far.

    solution
    import numpy as np
    import click
    from os import PathLike
    import copy
    
    class Day2Solver():
        def __init__(self, filepath: PathLike):
            with open(filepath) as infile:
                lines = infile.read().splitlines()
                self.reports = []
                for line in lines:
                    self.reports.append([int(x) for x in line.split()])
    
        
        def solve(self):
            num_safe = 0
            for r in self.reports:
                if is_slow_monotonic(r):
                    num_safe += 1
                else:
                    for i in range(len(r)):
                        new_report = copy.copy(r)
                        del new_report[i]
                        if is_slow_monotonic(new_report):
                            num_safe += 1
                            break
            return num_safe
    
    def is_slow_monotonic(line):
        diffs = []
        if len(line) == 0:
            return False
        for i in range(len(line)-1):
            diffs.append(line[i+1]-line[i])
        if not (all(d > 0 for d in diffs) or all(d < 0 for d in diffs)):
            return False
        if all(np.abs(d) >= 1 for d in diffs) and all(np.abs(d) <= 3 for d in diffs):
            return True
        else:
            return False
    
    @click.command()
    @click.argument('filename')
    def main(filename):
        p = Day2Solver(filename)
        print(p.solve())
    
    if __name__=="__main__":
        main()
    
    3 votes
  11. Crespyl
    Link
    Ruby solution def compute_p1(input) reports = input.lines.map { |line| line.split(/\s+/).map(&:to_i) } reports.map{ |r| report_safe?(r) }.count(true) end def report_safe?(report) return...
    Ruby solution
    def compute_p1(input)
      reports = input.lines.map { |line|
        line.split(/\s+/).map(&:to_i)
      }
      reports.map{ |r| report_safe?(r) }.count(true)
    end
    
    def report_safe?(report)
      return rule_1_all_inc_or_dec(report) && rule_2_step_size(report)
    end
    
    def rule_1_all_inc_or_dec(report)
      increasing = report[1] > report[0]
      idx = 0
      while (idx < report.length-1) do
        if increasing && (report[idx] > report[idx+1])
          return false
        elsif (not increasing) && (report[idx] < report[idx+1])
          return false
        elsif (report[idx] == report[idx+1])
          return false
        end
        idx += 1
      end
      return true
    end
    
    def rule_2_step_size(report)
      report.each_cons(2).map(&:sort).each do |a, b|
        diff = b - a
        if diff < 1 || diff > 3
          return false
        end
      end
      return true
    end
    
    def compute_p2(input)
      reports = input.lines.map { |line|
        line.split(/\s+/).map(&:to_i)
      }
      reports.map{ |r| report_safe_p2?(r) }.count(true)
    end
    
    def report_safe_p2?(report)
      if rule_1_all_inc_or_dec(report) && rule_2_step_size(report)
        return true
      else
        # try removing levels one at a time until the report passes
        for idx in (0...report.length)
          r = report.clone;
          r.delete_at(idx)
          if rule_1_all_inc_or_dec(r) && rule_2_step_size(r)
            return true
          end
        end
      end
      return false
    end
    
    2 votes
  12. tjf
    Link
    Here are my Python solutions. I'm not doing them right at midnight this year, just when I have the time. Part 1 import sys def is_safe(report: list[int]) -> bool: diffs = [a - b for a, b in...

    Here are my Python solutions. I'm not doing them right at midnight this year, just when I have the time.

    Part 1
    import sys
    
    def is_safe(report: list[int]) -> bool:
        diffs = [a - b for a, b in zip(report, report[1:])]
        return is_strictly_monotone(diffs) and is_gradual(diffs)
    
    def is_strictly_monotone(diffs: list[int]) -> bool:
        return abs(sum(abs(d) // d if d else 0 for d in diffs)) == len(diffs)
    
    def is_gradual(diffs: list[int]) -> bool:
        return all(1 <= abs(d) <= 3 for d in diffs)
    
    safe = sum(is_safe([*map(int, line.split())]) for line in sys.stdin)
    print(safe)
    
    Part 2
    import sys
    
    def is_safe(report: list[int], tolerate: bool = True) -> bool:
        diffs = [a - b for a, b in zip(report, report[1:])]
        if is_strictly_monotone(diffs) and is_gradual(diffs):
            return True
        if tolerate:
            for i in range(len(report)):
                subreport = report[:i] + report[i + 1:]
                if is_safe(subreport, tolerate=False):
                    return True
        return False
    
    def is_strictly_monotone(diffs: list[int]) -> bool:
        return abs(sum(abs(d) // d if d else 0 for d in diffs)) == len(diffs)
    
    def is_gradual(diffs: list[int]) -> bool:
        return all(1 <= abs(d) <= 3 for d in diffs)
    
    safe = sum(is_safe([*map(int, line.split())]) for line in sys.stdin)
    print(safe)
    
    2 votes
  13. fidwell
    (edited )
    Link
    Well I was already up until almost midnight so I decided to rush solving day 2. And we all know rushing leads to lots of silly mistakes and terrible code. Somehow it was enough to get me on top of...

    Well I was already up until almost midnight so I decided to rush solving day 2. And we all know rushing leads to lots of silly mistakes and terrible code. Somehow it was enough to get me on top of the Tildes leaderboard, although I'm sure I won't stay there.

    Today's was almost as straightforward as yesterday's; just a lot of array iteration and manipulation which isn't terribly interesting to go through algorithmically. I wrote a naïve solution that "brute-forced" it, though at this stage that's more than sufficient for single-digit-millisecond runtimes (I think 4ms for part 2). My biggest hangup was making copies of arrays in C#; it's not as straightforward as it is in other languages (or maybe that was due to rushing).

    Code — refactored. added some utility functions that made it (hopefully) very readable.

    1 vote