Monday, December 2, 2024

Advent of Code: Day 2

advent-of-code
Stephen Keane
Stephen Keane@skeane.io

Advent of Code: Day 2

Day 2: Red Nosed Reports

Day 2 has us working with a nuclear fusion plant! Check out Red-Nosed Reports for the day2 puzzle.

Part 1

For part 1, we need to go through each report and figure out whether the levels are safe.

My approach was to use one function to get the differences between each level and then use another function to check that the levels were safe.

This one was pretty straightforward. Sometimes, I'll find that my solution works on the testInput but not on the actual input. This time around, I was fortunate that the solution worked for both inputs on the first try.

testPath = "./testInput.txt"
inputPath = "./input.txt"
chosenPath = inputPath

def parseInput(path):
  file = open(path, "r")
  result = []
  for line in file.readlines():
      row = line.replace('
', '').split(' ')
      result.append(list(map(int, row)))
  return result

reports = parseInput(chosenPath)

def getLevels(reports):
  result = []
  for row in reports:
      list = []
      for i in range(len(row)):
          if i != len(row) - 1:
              list.append(row[i] - row[i + 1])
      result.append(list)
  return result

levels = (getLevels(reports))

def checkSafety(levels):
  orderedLevels = []
  # check that all values in a row are positive or negative
  for row in levels:
      if all(i > 0 for i in row):
          orderedLevels.append(row)
      elif all(i < 0 for i in row):
          orderedLevels.append(row)
          
  result = []
  #check that each number is between -3 and 3
  for row in orderedLevels:
      if all(i >= -3 for i in row) and all(i <= 3 for i in row):
              result.append(row)
  return result

safeLevels = checkSafety(levels)
print(len(safeLevels))

Part 2

Part 2 complicates things a bit by adding a 'problem dampener'.

For this variation we have to figure out if taking away any single level will result in the remaining levels being safe.

this was really tricky. I had to go back to my original solution and do a complete refactor: instead of parsing out each piece into it's own distinct function, I made one function with sub-functions to handle the different parts of the problem. In the end, I was able to get the solution.

testPath = "./testInput.txt"
inputPath = "./input.txt"
chosenPath = inputPath

def parseInput(path):
  file = open(path, "r")
  result = []
  for line in file.readlines():
      row = line.replace('
', '').split(' ')
      result.append(list(map(int, row)))
  return result

reports = parseInput(chosenPath)

def reviewReportLines(reports):
  safeReports = []
  for report in reports:
      # based on the report figure out the difference between each level.
      def getLevels(report):
          result = []
          for i in range(len(report)):
              if i != len(report) - 1:
                  result.append(report[i] - report[i + 1])
          return result
      # check to see if a report is safe (all levels are either all negative or all positive)
      # and no level is greater than +3 or less than -3
      def checkSafety(report):
          levels = getLevels(report)
          isSafe = lambda levels: all(i > 0 and i <= 3 for i in levels) or all(i < 0 and i >= -3 for i in levels)
          if isSafe(levels):
              safeReports.append(report)
          # if the report is not safe, loop through the report, remove one level at a time and check 
          # if the report is safe. If it is, add it to the safeReports array.    
          if not isSafe(levels):
              for i in range(len(report)):
                  newReport = report.copy()
                  newReport.pop(i)
                  newLevels = getLevels(newReport)
                  if isSafe(newLevels):
                      safeReports.append(newReport)
                      break
      checkSafety(report)
  return safeReports

safeReports = reviewReportLines(reports)

print(len(safeReports))