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
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/22323136
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
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 ;
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)
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 }
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))))
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(); }
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
#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; }
}
`
Rust
Turned out alright, I am looking forward to seeing what 2d coordinate grid code I can cannibalize from last year’s solutions 😄
I think that repo is private
I realized after I posted 😅 thanks for pointing it out! I will go make it public
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
This is my very naive rust solution, part 2 is mostly just an extra function, so they’re bother covered in this one.
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}
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)
.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.
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()
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!();
is_sorted_by
is new to me, could be very useful.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.
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.