aoc-2024/day2/part2.lua
2024-12-02 14:05:55 -08:00

126 lines
2.7 KiB
Lua

-- see if the file exists
local function file_exists(file)
local f = io.open(file, "rb")
if f then
f:close()
end
return f ~= nil
end
-- get all lines from a file, returns an empty
-- list/table if the file does not exist
local function lines_from(file)
if not file_exists(file) then
return {}
end
local lines = {}
for line in io.lines(file) do
lines[#lines + 1] = line
end
return lines
end
local function splitstr(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t = {}
for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
t[#t + 1] = str
end
return t
end
-- tests the functions above
local file = "input"
local lines = lines_from(file)
local reports = {}
for _, line in ipairs(lines) do
local raw_report = splitstr(line)
for i, v in ipairs(raw_report) do
raw_report[i] = tonumber(v)
end
reports[#reports + 1] = raw_report
end
-- return the sign of a number
local function sign(number)
if number > 0 then
return 1
elseif number == 0 then
return 0
else
return -1
end
end
function table.shallow_copy(t)
local t2 = {}
for k, v in pairs(t) do
t2[k] = v
end
return t2
end
local function is_safe(report)
local s = nil -- keeps track of the sign over the course of a report
local unsafe = false
local unsafe_idx_1, unsafe_idx_2
for i, v1 in ipairs(report) do
local v2 = report[i + 1]
-- if v2 doesn't exist, we are at the end of the report
if v2 ~= nil then
if s == nil then
-- if we are on the first iteration, we need to set the direction for
-- the rest of the report
s = sign(v1 - v2)
else
-- we check that the sign matches the rest of the report, breaking early if not
if sign(v1 - v2) ~= s then
unsafe = true
unsafe_idx_1, unsafe_idx_2 = i, i + 1
break
end
end
-- Checking if the rate of change in the report is within acceptable bounds
local distance = math.abs(v1 - v2)
if distance < 1 or distance > 3 then
unsafe = true
unsafe_idx_1, unsafe_idx_2 = i, i + 1
break
end
end
end
return unsafe, unsafe_idx_1, unsafe_idx_2
end
-- Copy the report, remove the specified index, and test if it is safe
local function test_altered_report(report, idx)
local test_report = table.shallow_copy(report)
table.remove(test_report, idx)
return is_safe(test_report)
end
local safe_count = 0
for _, report in ipairs(reports) do
-- if unsafe == true, idx1 and idx2 will be null
local unsafe, idx1, idx2 = is_safe(report)
if unsafe then
-- if the report is unsafe, generate 2 reports with the potentially bad values removed, and test them again
if not test_altered_report(report, idx1) or not test_altered_report(report, idx2) then
unsafe = false
end
end
if not unsafe then
safe_count = safe_count + 1
end
end
print(safe_count)