Run
aoc_source(day = 2, part = 1)
input = aoc_read(day = 2)
aoc_run(solve_day2_part1(input))
Elapsed: 0.015 seconds
Memory: 58 KB
Day 1 Day 2 Day 3 Day 4 Day 5 Day 6 Day 7 Day 8 Day 9 Day 10 Day 11 Day 12 Day 13 Day 14 Day 15 Day 16
Part 1 was straightforward. For part 2 I took what seems to be a long-winded approach of trying to identify which levels caused unsafe differences. Then I tried reports with those levels removed and tested if they were safe. This included checking for a majority direction of differences and replacing the differences in the minority, but on my first attempt I forgot to deal with no majority!
I’ve seen from others that it’s simpler to try removing each and every level, which is plenty feasible with the input. I preferred to use this simpler logic in my Python implementation as working with its lists and numpy arrays are new to me.
#' Find how many reports are safe
#'
#' Check each report is
#' either all decreasing OR all increasing
#' AND all absolute differences are within 1 to 3
#'
#' @param input
#' A character vector of strings containing space separated digits, where
#' each string is a report and each digit is a 'level'
#'
#' @return
#' numeric(1) The number of safe reports.
solve_day2_part1 <- function(input) {
num_list <- strsplit(input, " ")
num_list <- lapply(num_list, as.numeric)
diff_list <- lapply(num_list, diff)
sum(vapply(diff_list, is_safe, logical(1)))
}
#' Check if the differences are safe
#'
#' Check the differences for
#' either all less than zero OR all greater than zero
#' AND all absolute differences are within 1 to 3
#'
#' @param diffs
#' Numeric differences between levels in a report
#'
#' @return
#' logical(1) whether the report differences are safe
is_safe <- function(diffs) {
one_direction <- all(diffs < 0) | all(diffs > 0)
safe_size <- all(abs(diffs) %in% c(1, 2, 3))
one_direction & safe_size
}
aoc_source(day = 2, part = 1)
input = aoc_read(day = 2)
aoc_run(solve_day2_part1(input))
Elapsed: 0.015 seconds
Memory: 58 KB
#' Find how many reports are safe with dampeners
#'
#' Check each report is
#' either all decreasing OR all increasing
#' AND all absolute differences are within 1 to 3
#'
#' As well as whether the same can be achieved by removing one of the levels.
#'
#' @param input
#' A character vector, where each represents a report, containing space
#' delimited sequence of digits, 'levels'
#'
#' @return
#' numeric(1) The number of safe reports.
solve_day2_part2 <- function(input) {
num_list <- strsplit(input, " ")
num_list <- lapply(num_list, as.numeric)
safe_list <- vapply(num_list, is_safe, logical(1))
sum(safe_list)
}
#' Check if a report is safe
#'
#' Find the majority direction in differences (increasing/decreasing) if there
#' is one. Mark the minority direction as unsafe diffs. Mark absolute diffs
#' that aren't in 1, 2, 3 as unsafe. For each unsafe diff check if the report
#' could be safe with new reports made by removing the levels either side of
#' it.
#'
#' If there is no majority direction then the report is unsafe because the
#' dampener won't be enough. (I suppose this assumes report length is not 3,
#' which could be made safe)
#'
#' @param report
#' Numeric levels to be checked
#'
#' @return
#' logical(1) whether the report is safe
is_safe <- function(report, removed = 0) {
diffs <- diff(report)
majority_diffs <- length(diffs) / 2 + 1
# no majority direction
# fails all increasing OR all decreasing rule
# dampener won't be enough
if (max(sum(diffs < 0), sum(diffs > 0)) < majority_diffs) {
return(FALSE)
}
# logical to store location of unsafe diffs
unsafe_diffs <- logical(length(diffs))
# if majority decreasing, unsafe where increasing
if (sum(diffs < 0) >= 3) {
unsafe_diffs <- diffs > 0
}
# if majority increasing, unsafe where decreasing
if (sum(diffs > 0) >= 3) {
unsafe_diffs <- unsafe_diffs | diffs < 0
}
# unsafe where difference not in safe size range
unsafe_diffs <- unsafe_diffs | !abs(diffs) %in% c(1, 2, 3)
if (!any(unsafe_diffs)) {
# no unsafe levels
TRUE
} else {
# create new reports by removing each level either side of the unsafe diffs
unsafe_levels <- which(unsafe_diffs)
unsafe_levels <- c(unsafe_levels, unsafe_levels + 1)
new_reports <- lapply(unsafe_levels, \(level) report[-level])
# check if the shortened report is safe without further option
# to remove levels
any(vapply(new_reports, is_safe_no_dampener, logical(1)))
}
}
#' Check if the rest of a report is safe
#'
#' Check the differences for
#' either all less than zero OR all greater than zero
#' AND all absolute differences are within 1 to 3
#'
#' @param report
#' Numeric levels to be checked
#'
#' @return
#' logical(1) whether the report is safe
is_safe_no_dampener <- function(report) {
diffs <- diff(report)
one_direction <- all(diffs < 0) | all(diffs > 0)
safe_size <- all(abs(diffs) %in% c(1, 2, 3))
one_direction & safe_size
}
aoc_source(day = 2, part = 2)
input = aoc_read(day = 2)
aoc_run(solve_day2_part2(input))
Elapsed: 0.049 seconds
Memory: 193 KB
import numpy as np
def solve_day2_part1(text):
"""
Find how many reports are safe
Check each report is
either all decreasing OR all increasing
AND all absolute differences are within 1 to 3
Parameters
----------
text : list of str
Where each string is a report and each digit is a 'level'
Returns
-------
int
The number of safe reports
"""
reports_list = [line.split() for line in text]
reports_list = [np.array(report, dtype = np.int32) for report in reports_list]
diffs_list = [np.diff(report) for report in reports_list]
return(sum([is_safe(diffs) for diffs in diffs_list]))
def is_safe(diffs):
"""
Check if the differences are safe
Check the differences for
either all decreasing OR all increasing
AND all absolute differences are within 1 to 3
Parameters
----------
diffs : numpy array of int
Where each int is a difference between consecutive levels in a report
Returns
-------
bool
Whether the differences are safe
"""
one_direction = all(diffs < 0) or all(diffs > 0)
safe_size = all(abs(diffs) <= 3)
return(one_direction and safe_size)
import numpy as np
def solve_day2_part2(text):
"""
Find how many reports are safe
Check each report is
either all decreasing OR all increasing
AND all absolute differences are within 1 to 3
Parameters
----------
text : list of str
Where each string is a report and each digit is a 'level'
Returns
-------
int
The number of safe reports
"""
reports_list = [line.split() for line in text]
reports_list = [np.array(report, dtype = np.int32) for report in reports_list]
return(sum([is_safe(report) for report in reports_list]))
def is_safe(report):
"""
Check if a report is safe with a dampener
If a report is not ordinarily safe, try removing each single level to
then check if is safe.
Parameters
----------
report : numpy array of int
Where each int is a level in the report
Returns
-------
bool
Whether the differences are safe
"""
if (is_safe_no_dampener(report)):
return(True)
for i in range(len(report)):
new_report = np.delete(report, i)
if (is_safe_no_dampener(new_report)):
return(True)
return(False)
def is_safe_no_dampener(report):
"""
Check if a report is safe
Check the report is safe by finding the differences and confirming:
either all decreasing OR all increasing
AND all absolute differences are within 1 to 3
Parameters
----------
report : numpy array of int
Where each int is a level in the report
Returns
-------
bool
Whether the differences are safe
"""
diffs = np.diff(report)
one_direction = all(diffs < 0) or all(diffs > 0)
safe_size = all(abs(diffs) <= 3)
return(one_direction and safe_size)