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
Elixir
defmodule AdventOfCode.Solution.Year2024.Day02 do
use AdventOfCode.Solution.SharedParse
@impl true
def parse(input) do
for line <- String.split(input, "\n", trim: true),
do: String.split(line) |> Enum.map(&String.to_integer/1)
end
def part1(input) do
Enum.count(input, &is_safe(&1, false))
end
def part2(input) do
Enum.count(input, &(is_safe(&1, true) or is_safe(tl(&1), false)))
end
def is_safe([a, b, c | rest], can_fix) do
cond do
(b - a) * (c - b) > 0 and abs(b - a) in 1..3 and abs(c - b) in 1..3 ->
is_safe([b, c | rest], can_fix)
can_fix ->
is_safe([a, c | rest], false) or is_safe([a, b | rest], false)
true ->
false
end
end
def is_safe(_, _), do: true
end
J
There is probably a way to write this more point-free. You can definitely see here the friction involved in the way J wants to regard lists as arrays: short rows of the input matrix are zero padded, so you have to snip off the padding before you process each row, and that means you can’t lift some of the operations back up to the parent matrix because it will re-introduce the padding as it reshapes the result; this accounts for a lot of the "1
everywhere (you can interpret v"1
as “force the verb v
to operate on rank 1 subarrays of the argument”).
data_file_name =: '2.data'
data =: > 0 ". each cutopen toJ fread data_file_name
NB. {. take, i. index of; this removes trailing zeros
remove_padding =: {.~ i.&0
NB. }. behead, }: curtail; this computes successive differences
diff =: }. - }:
NB. a b in_range y == a <: y <: b
in_range =: 4 : '(((0 { x) & <:) * (<: & (1 { x))) y'
NB. a row is safe if either all successive differences are in [1..3] or all in [_3.._1]
NB. +. or
ranges =: 2 2 $ 1 3 _3 _1
row_safe =: (+./"1) @: (*/"1) @: (ranges & (in_range"1 _)) @: diff @: remove_padding
result1 =: +/ safe"1 data
NB. x delete y is y without the xth element
delete =: 4 : '(x {. y) , ((>: x) }. y)'"0 _
modified_row =: 3 : 'y , (i.#y) delete y'
modified_row_safe =: 3 : '+./"1 row_safe"1 modified_row"1 y'
result2 =: +/ modified_row_safe data
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.
TypeScript
Solution
import { AdventOfCodeSolutionFunction } from "./solutions";
/**
* this function evaluates the
* @param levels a list to check
* @returns -1 if there is no errors, or the index of where there's an unsafe event
*/
export function EvaluateLineSafe(levels: Array<number>) {
// this loop is the checking every number in the line
let isIncreasing: boolean | null = null;
for (let levelIndex = 1; levelIndex < levels.length; levelIndex++) {
const prevLevel = levels[levelIndex - 1]; // previous
const level = levels[levelIndex]; // current
const diff = level - prevLevel; // difference
const absDiff = Math.abs(diff); // absolute difference
// check if increasing too much or not at all
if (absDiff == 0 || absDiff > 3)
return levelIndex; // go to the next report
// set increasing if needed
if (isIncreasing === null) {
isIncreasing = diff > 0;
continue; // compare the next numbers
}
// check if increasing then decreasing
if (!(isIncreasing && diff > 0 || !isIncreasing && diff < 0))
return levelIndex; // go to the next report
}
return -1;
}
export const solution_2: AdventOfCodeSolutionFunction = (input) => {
const reports = input.split("\n");
let safe = 0;
let safe_damp = 0;
// this loop is for every line
main: for (let i = 0; i < reports.length; i++) {
const report = reports[i].trim();
if (!report)
continue; // report is empty
const levels = report.split(" ").map((v) => Number(v));
const evaluation = EvaluateLineSafe(levels);
if(evaluation == -1) {
safe++;
continue;
}
// search around where it failed
for (let offset = evaluation - 2; offset <= evaluation + 2; offset++) {
// delete an evaluation in accordance to the offset
let newLevels = [...levels];
newLevels.splice(offset, 1);
const newEval = EvaluateLineSafe(newLevels);
if(newEval == -1) {
safe_damp++;
continue main;
}
}
}
return `Part 1: ${safe} Part 2: ${safe + safe_damp}`;
}
God, I really wish my solutions weren’t so convoluted. Also, this is an O(N^3) solution…
It’s not as simple as that. You can have 20 nested for loops with complexity of O(1) if all of them only ever finish one iteration.
Or you can have one for loop that iterates 2^N times.
Haskell
Had some fun with arrows.
import Control.Arrow
import Control.Monad
main = getContents >>= print . (part1 &&& part2) . fmap (fmap read . words) . lines
part1 = length . filter isSafe
part2 = length . filter (any isSafe . removeOne)
isSafe = ap (zipWith (-)) tail >>> (all (between 1 3) &&& all (between (-3) (-1))) >>> uncurry (||)
where
between a b = (a <=) &&& (<= b) >>> uncurry (&&)
removeOne [] = []
removeOne (x : xs) = xs : fmap (x :) (removeOne xs)