Day 2: Red-Nosed Reports

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://blocks.programming.dev/ if you prefer sending it through a URL

FAQ

  • Andy@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    5 hours ago

    Factor

    : get-input ( -- reports )
      "aoc-2024.02" "input.txt" vocab-file-lines
      [ split-words [ string>number ] map ] map ;
    
    : slanted? ( report -- ? )
      { [ [ > ] monotonic? ] [ [ < ] monotonic? ] } || ;
    
    : gradual? ( report -- ? )
      [ - abs 1 3 between? ] monotonic? ;
    
    : safe? ( report -- ? )
      { [ slanted? ] [ gradual? ] } && ;
    
    : part1 ( -- n )
      get-input [ safe? ] count ;
    
    : fuzzy-reports ( report -- reports )
      dup length <iota> [ remove-nth-of ] with map ;
    
    : tolerable? ( report -- ? )
      { [ safe? ] [ fuzzy-reports [ safe? ] any? ] } || ;
    
    : part2 ( -- n )
      get-input [ tolerable? ] count ;
    
  • bugsmith@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    7 hours ago

    Elixir

    defmodule Day02 do
      defp part1(reports) do
        reports
        |> Enum.map(fn report ->
          levels =
            report
            |> String.split()
            |> Enum.map(&String.to_integer/1)
    
          cond do
            sequence_is_safe?(levels) ->
              :safe
    
            true ->
              :unsafe
          end
        end)
        |> Enum.count(fn x -> x == :safe end)
      end
    
      defp part2(reports) do
        reports
        |> Enum.map(fn report ->
          levels =
            report
            |> String.split()
            |> Enum.map(&String.to_integer/1)
    
          sequences =
            0..(length(levels) - 1)
            |> Enum.map(fn i ->
              List.delete_at(levels, i)
            end)
    
          cond do
            sequence_is_safe?(levels) ->
              :safe
    
            Enum.any?(sequences, &sequence_is_safe?/1) ->
              :safe
    
            true ->
              :unsafe
          end
        end)
        |> Enum.count(fn x -> x == :safe end)
      end
    
      defp all_gaps_within_max_diff?(numbers) do
        numbers
        |> Enum.chunk_every(2, 1, :discard)
        |> Enum.all?(fn [a, b] -> abs(b - a) <= 3 end)
      end
    
      defp is_strictly_increasing?(numbers) do
        numbers
        |> Enum.chunk_every(2, 1, :discard)
        |> Enum.all?(fn [a, b] -> a < b end)
      end
    
      defp is_strictly_decreasing?(numbers) do
        numbers
        |> Enum.chunk_every(2, 1, :discard)
        |> Enum.all?(fn [a, b] -> a > b end)
      end
    
      defp sequence_is_safe?(numbers) do
        (is_strictly_increasing?(numbers) or
           is_strictly_decreasing?(numbers)) and all_gaps_within_max_diff?(numbers)
      end
    
      def run(data) do
        reports = data |> String.split("\n", trim: true)
        p1 = part1(reports)
        p2 = part2(reports)
        IO.puts(p1)
        IO.puts(p2)
      end
    end
    
    data = File.read!("input.in")
    Day02.run(data)
    
  • proved_unglue@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    8 hours ago

    Kotlin

    A bit late to the party, but here you go.

    import kotlin.math.abs
    
    fun part1(input: String): Int {
        return solve(input, ::isSafe)
    }
    
    fun part2(input: String): Int {
        return solve(input, ::isDampSafe)
    }
    
    private fun solve(input: String, condition: (List<Int>) -> Boolean): Int {
        var safeCount = 0
        input.lines().forEach { line ->
            if (line.isNotBlank()) {
                val nums = line.split("\\s+".toRegex()).map { it.toInt() }
                safeCount += if (condition(nums)) 1 else 0
            }
        }
        return safeCount
    }
    
    private fun isSafe(list: List<Int>): Boolean {
        val safeDiffs = setOf(1, 2, 3)
        var incCount = 0
        var decCount = 0
        for (idx in 0..<list.lastIndex) {
            if (!safeDiffs.contains(abs(list[idx] - list[idx + 1]))) {
                return false
            }
            if (list[idx] <= list[idx + 1]) incCount++
            if (list[idx] >= list[idx + 1]) decCount++
        }
        return incCount == 0 || decCount == 0
    }
    
    private fun isDampSafe(list: List<Int>): Boolean {
        if (isSafe(list)) {
            return true
        } else {
            for (idx in 0..list.lastIndex) {
                val shortened = list.toMutableList()
                shortened.removeAt(idx)
                if (isSafe(shortened)) {
                    return true
                }
            }
        }
        return false
    }
    
  • wer2@lemm.ee
    link
    fedilink
    arrow-up
    2
    ·
    16 hours ago

    Lisp

    Part 1
    (defun p1-process-line (line)
      (mapcar #'parse-integer (str:words line)))
    
    (defun line-direction-p (line)
      "make sure the line always goes in the same direction"
      (loop for x in line
            for y in (cdr line)
            count (> x y) into dec
            count (< x y) into inc
            when (and (> dec 0 ) (> inc 0)) return nil
            when (= x y) return nil
            finally (return t)))
    
    (defun line-in-range-p (line)
      "makes sure the delta is within 3"
      (loop for x in line
            for y in (cdr line)
            for delta = (abs (- x y))
            when (or (> delta 3) )
              return nil 
            finally (return t)))
    
    (defun test-line-p (line)
      (and (line-in-range-p line) (line-direction-p line)))
    
    (defun run-p1 (file) 
      (let ((data (read-file file #'p1-process-line)))
        (apply #'+ (mapcar (lambda (line) (if (test-line-p line) 1 0)) data))))
    
    
    Part 2
    (defun test-line-p2 (line)
      (or (test-line-p (cdr line))
          (test-line-p (cdr (reverse line)))
      (loop for back on line
            collect (car back) into front
            when (test-line-p (concatenate 'list front (cddr back)))
              return t
            finally (return nil)
      )))
    
    (defun run-p2 (file) 
      (let ((data (read-file file #'p1-process-line)))
        (loop for line in data
              count (test-line-p2 line))))
    
    
  • hades@lemm.ee
    link
    fedilink
    arrow-up
    1
    ·
    15 hours ago

    C#

    using MathNet.Numerics.LinearAlgebra;
    public class Day02 : Solver
    {
      private ImmutableList<Vector<Double>> data;
    
      public void Presolve(string input)
      {
        data = input.Trim().Split("\n")
            .Select(
                line => Vector<Double>.Build.DenseOfEnumerable(line.Split(' ').Select(double.Parse))
            ).ToImmutableList();
      }
    
      private bool IsReportSafe(Vector<Double> report) {
        Vector<Double> delta = report.SubVector(1, report.Count - 1)
            .Subtract(report.SubVector(0, report.Count - 1));
        return (delta.ForAll(x => x > 0) || delta.ForAll(x => x < 0))
            && Vector<Double>.Abs(delta).Max() <= 3;
      }
    
      private bool IsDampenedReportSafe(Vector<Double> report) {
        for (Double i = 0; i < report.Count; ++i) {
          var dampened = Vector<Double>.Build.DenseOfEnumerable(
                report.EnumerateIndexed()
                    .Where(item => item.Item1 != i)
                    .Select(item => item.Item2));
          if (IsReportSafe(dampened)) return true;
        }
        return false;
      }
    
      public string SolveFirst() => data.Where(IsReportSafe).Count().ToString();
    
      public string SolveSecond() => data.Where(IsDampenedReportSafe).Count().ToString();
    }
    
  • sjmulder@lemmy.sdf.org
    link
    fedilink
    arrow-up
    2
    ·
    20 hours ago

    JavaScript

    Also wrote a solution in JavaScript to play around with list comprehension. Wrote some utility functions for expressiveness (and lazy evaluation).

    Code
    const fs = require("fs");
    const U = require("./util");
    
    const isSafe = xs =>
        U.pairwise(xs).every(([a,b]) => a!==b && a-b > -4 && a-b < 4) &&
        new Set(U.pairwise(xs).map(([a,b]) => a < b)).size === 1;
    
    const rows = fs
        .readFileSync(process.argv[2] || process.stdin.fd, "utf8")
        .split("\n")
        .filter(x => x != "")
        .map(x => x.split(/ +/).map(Number));
    
    const p1 = U.countBy(rows, isSafe);
    const p2 = U.countBy(rows, row =>
        isSafe(row) || U.someBy(U.indices(row),
            i => isSafe([...row.slice(0, i), ...row.slice(i+1)])));
    
    console.log("02:", p1, p2);
    

    https://github.com/sjmulder/aoc/blob/master/2024/js/day02.js

  • Nighed
    link
    fedilink
    English
    arrow-up
    4
    ·
    edit-2
    23 hours ago

    #Rust

    initially, for part two I was trying to ignore a bad pair not a bad value - read the question!

    Only installed Rust on Sunday, day 1 was a mess, today was more controlled. Need to look at some of the rust solutions for std library methods I don’t know about.

    very focussed on getting it to actually compile/work over making it short or nice!

    long!

    `

    pub mod task_2 {

    pub fn task_1(input: &str) -> i32{
        let mut valid_count = 0;
    
        let reports = process_input(input);
    
        for report in reports{
            let valid = is_report_valid(report);
    
            if valid{
                valid_count += 1;
            }
        }
    
        println!("Valid count: {}", valid_count);
        valid_count
    }
    
    pub fn task_2(input: &str) -> i32{
        let mut valid_count = 0;
    
        let reports = process_input(input);
    
        for report in reports{
            let mut valid = is_report_valid(report.clone());
    
            if !valid
            {
                for position_to_delete in 0..report.len()
                {
                    let mut updated_report = report.clone();
                    updated_report.remove(position_to_delete);
                    valid = is_report_valid(updated_report);
    
                    if valid { break; }
                }
            }
    
            if valid{
                valid_count += 1;
            }
        }
    
        println!("Valid count: {}", valid_count);
        valid_count
    }
    
    fn is_report_valid(report:Vec<i32>) -> bool{
        let mut increasing = false;
        let mut decreasing = false;
        let mut valid = true;
    
        for position in 1..report.len(){
            if report[position-1] > report[position]
            {
                decreasing = true;
            }
            else if report[position-1] < report[position]
            {
                increasing = true;
            }
            else
            {
                valid = false;
                break;
            }
    
            if (report[position-1] - report[position]).abs() > 3
            {
                valid = false;
                break;
            }
    
            if increasing && decreasing
            {
                valid = false;
                break;
            }
        }
    
        return valid;
    }
    
    pub fn process_input(input: &str) -> Vec<Vec<i32>>{
        let mut reports: Vec<Vec<i32>> = Vec::new();
        for report_string in input.split("\n"){
            let mut report: Vec<i32> = Vec::new();
            for value in report_string.split_whitespace() {
                report.push(value.parse::<i32>().unwrap());
            }
            reports.push(report);
        }
    
        return reports;
    }
    

    }

    `

  • antlion@lemmy.dbzer0.com
    link
    fedilink
    arrow-up
    1
    ·
    18 hours ago

    R (R-Wasm)

    input = file('input2024day2.txt',open='r')
    lines = readLines(input)
    library(stringr)
    safe = 0
    safe2 = 0
    for (ln in lines){
      vals = as.numeric(unlist(str_split(ln,' ')))
      diffs = diff(vals)
      cond1 = min(diffs) > 0 || max(diffs) < 0
      cond2 = max(abs(diffs)) < 4
      if (cond1 && cond2){
        safe = safe + 1
      }
      else { #Problem Dampener
        dampen = FALSE
        for (omit in -1:-length(vals)){
          diffs = diff(vals[omit])
          cond1 = min(diffs) > 0 || max(diffs) < 0
          cond2 = max(abs(diffs)) < 4
          if (cond1 && cond2){
            dampen = TRUE
          }
        }
        if (dampen){
          safe2 = safe2 + 1}
      }
    }
    print (safe) #Part 1
    print (safe + safe2) #Part 2
    
  • Reptorian@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    20 hours ago

    G’MIC solution

    spoiler
    it day2
    crop. 0,0,0,{h#-1-2}
    split. -,{_'\n'}
    foreach { replace_str. " ",";" ({t}) rm.. }
    
    safe_0,safe_1=0
    foreach {
    	({h}) a[-2,-1] y
    	num_of_attempts:=da_size(#-1)+1
    	store temp
    
    	repeat $num_of_attempts {
    
    		$temp
    
    		if $> eval da_remove(#-1,$>-1) fi
    
    		eval "
    			safe=1;
    			i[#-1,1]>i[#-1,0]?(
    				for(p=1,p<da_size(#-1),++p,
    					if(!inrange(i[#-1,p]-i[#-1,p-1],1,3,1,1),safe=0;break(););
    				);
    			):(
    				for(p=1,p<da_size(#-1),++p,
    					if(!inrange(i[#-1,p-1]-i[#-1,p],1,3,1,1),safe=0;break(););
    				);
    			);
    			safe;"
    
    		rm
    
    		if $>
    			if ${} safe_1+=1 break fi
    		else
    			if ${} safe_0,safe_1+=1 break fi
    		fi
    
    	}
    
    }
    
    echo Day" "2:" "${safe_0}" :: "${safe_1}
    
  • Zarlin@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    24 hours ago

    Nim

    import strutils, times, sequtils, sugar
    
    # check if level transition in record is safe
    proc isSafe*(sign:bool, d:int): bool =
      sign == (d>0) and d.abs in 1..3;
    
    #check if record is valid
    proc validate*(record:seq[int]): bool =
      let sign = record[0] > record[1];
      return (0..record.len-2).allIt(isSafe(sign, record[it] - record[it+1]))
    
    # check if record is valid as-is
    # or if removing any item makes the record valid
    proc validate2*(record:seq[int]): bool =
      return record.validate or (0..<record.len).anyIt(record.dup(delete(it)).validate)
    
    proc solve*(input:string): array[2,int] =
      let lines = input.readFile.strip.splitLines;
      let records = lines.mapIt(it.splitWhitespace.map(parseInt));
      result[0] = records.countIt(it.validate);
      result[1] = records.countIt(it.validate2);
    

    I got stuck on part 2 trying to check everything inside a single loop, which kept getting more ugly. So then I switched to just deleting one item at a time and re-checking the record.

    Reworked it after first finding the solution to compress the code a bit, though the range iterators don’t really help with readability.

    I did learn about the sugar import, which I used to make the sequence duplication more compact: record.dup(delete(it).

    • janAkali@lemmy.one
      link
      fedilink
      English
      arrow-up
      2
      ·
      edit-2
      14 hours ago

      Cool to see another solution in Nim here =)

      (0..<record.len).anyIt(record.dup(delete(it)).validate)

      That’s smart. I haven’t thought of using iterators to loop over indexes (except in a for loop).

      I got stuck on part 2 trying to check everything inside a single loop, which kept getting more ugly.

      Yeah I’ve thought of simple ways to do this and found none. And looking at the input - it’s too easy to bruteforce, especially in compiled lang like Nim.

  • TunaCowboy@lemmy.world
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    19 hours ago

    python

    solution
    import re
    import aoc
    
    def setup():
        return (aoc.get_lines(2), 0)
    
    def safe(data):
        order = 0 if data[0] < data[1] else 1
        for i in range(0, len(data) - 1):
            h = data[i]
            t = data[i + 1]
            d = abs(h - t)
            if d not in [1, 2, 3] or (order == 0 and h >= t) or (
                    order == 1 and h <= t):
                return False
        return True
    
    def one():
        lines, acc = setup()
        for l in lines:
            if safe([int(x) for x in re.findall(r'\d+', l)]):
                acc += 1
        print(acc)
    
    def two():
        lines, acc = setup()
        for l in lines:
            data = [int(x) for x in re.findall(r'\d+', l)]
            for i in range(len(data)):
                if safe(data[:i] + data[i + 1:]):
                    acc += 1
                    break
        print(acc)
    
    one()
    two()
    
  • Gobbel2000@programming.dev
    link
    fedilink
    English
    arrow-up
    5
    ·
    1 day ago

    Rust

    The function is_sorted_by on Iterators turned out helpful for compactly finding if a report is safe. In part 2 I simply tried the same with each element removed, since all reports are very short.

    fn parse(input: String) -> Vec<Vec<i32>> {
        input.lines()
            .map(|l| l.split_whitespace().map(|w| w.parse().unwrap()).collect())
            .collect()
    }
    
    fn is_safe(report: impl DoubleEndedIterator<Item=i32> + Clone) -> bool {
        let safety = |a: &i32, b: &i32| (1..=3).contains(&(b - a));
        report.clone().is_sorted_by(safety) || report.rev().is_sorted_by(safety)
    }
    
    fn part1(input: String) {
        let reports = parse(input);
        let safe = reports.iter().filter(|r| is_safe(r.iter().copied())).count();
        println!("{safe}");
    }
    
    fn is_safe2(report: &[i32]) -> bool {
        (0..report.len()).any(|i| {  // Try with each element removed
            is_safe(report.iter().enumerate().filter(|(j, _)| *j != i).map(|(_, n)| *n))
        })
    }
    
    fn part2(input: String) {
        let reports = parse(input);
        let safe = reports.iter().filter(|r| is_safe2(r)).count();
        println!("{safe}");
    }
    
    util::aoc_main!();
    
    • Sleepless One@lemmy.ml
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 day ago

      The is_sorted_by is a really nice approach. I originally tried using that function thinking that |a, b| a > b or |a, b| a < b would cut it but it didn’t end up working. I never thought to handle the check for the step being between 1 and 3 in the callback closure for that though.

  • Sleepless One@lemmy.ml
    link
    fedilink
    English
    arrow-up
    3
    ·
    1 day ago

    Rust

    use crate::utils::read_lines;
    
    pub fn solution1() {
        let reports = get_reports();
        let safe_reports = reports
            .filter(|report| report.windows(3).all(window_is_valid))
            .count();
    
        println!("Number of safe reports = {safe_reports}");
    }
    
    pub fn solution2() {
        let reports = get_reports();
        let safe_reports = reports
            .filter(|report| {
                (0..report.len()).any(|i| {
                    [&report[0..i], &report[i + 1..]]
                        .concat()
                        .windows(3)
                        .all(window_is_valid)
                })
            })
            .count();
    
        println!("Number of safe reports = {safe_reports}");
    }
    
    fn window_is_valid(window: &[usize]) -> bool {
        matches!(window[0].abs_diff(window[1]), 1..=3)
            && matches!(window[1].abs_diff(window[2]), 1..=3)
            && ((window[0] > window[1] && window[1] > window[2])
                || (window[0] < window[1] && window[1] < window[2]))
    }
    
    fn get_reports() -> impl Iterator<Item = Vec<usize>> {
        read_lines("src/day2/input.txt").map(|line| {
            line.split_ascii_whitespace()
                .map(|level| {
                    level
                        .parse()
                        .expect("Reactor level is always valid integer")
                })
                .collect()
        })
    }
    

    Definitely trickier than yesterday’s. I feel like the windows solution isn’t the best, but it was what came to mind and ended up working for me.