finished day 4

This commit is contained in:
gabe 2022-08-21 20:00:23 -05:00
parent e6354cf097
commit 122e48ef5d
5 changed files with 1356 additions and 0 deletions

132
day4/day4.py Executable file
View file

@ -0,0 +1,132 @@
#! /usr/bin/env python3
import pathlib
import sys
import re
from dataclasses import dataclass
from pprint import pprint
@dataclass
class Passport:
birthYear: int | None
issueYear: int | None
expirationYear: int | None
height: str | None
hairColor: str | None
eyeColor: str | None
passportId: str | None
countryId: str | None
def regexUnwrap(match, group:int)->str | None:
return match[group] if match is not None else None
def parse(puzzle_input: str)->list[Passport]:
"""Parse input"""
rawRecords = [i.replace('\n', ' ') for i in puzzle_input.split('\n\n')]
records = []
for rawRecord in rawRecords:
birthYear=re.search(r'byr:(\S+)', rawRecord)
issueYear=re.search(r'iyr:(\S+)', rawRecord)
expirationYear=re.search(r'eyr:(\S+)', rawRecord)
height=re.search(r'hgt:(\S+)', rawRecord)
hairColor=re.search(r'hcl:(\S+)', rawRecord)
eyeColor=re.search(r'ecl:(\S+)', rawRecord)
passportId=re.search(r'pid:(\S+)', rawRecord)
countryId=re.search(r'cid:(\S+)', rawRecord)
records.append(Passport(
int(birthYear[1]) if birthYear is not None else None,
int(issueYear[1]) if issueYear is not None else None,
int(expirationYear[1]) if expirationYear is not None else None,
regexUnwrap(height,1),
regexUnwrap(hairColor,1),
regexUnwrap(eyeColor,1),
regexUnwrap(passportId,1),
regexUnwrap(countryId,1),
))
return records
def lazyCheckPassport(passport: Passport)->bool:
return (
passport.birthYear is not None
and passport.issueYear is not None
and passport.expirationYear is not None
and passport.height is not None
and passport.hairColor is not None
and passport.eyeColor is not None
and passport.passportId is not None
)
def part1(data):
"""Solve part 1"""
return sum(1 for record in data if lazyCheckPassport(record))
def checkBirthYear(passport: Passport)->bool:
if passport.birthYear is None: return False
return (1920<=passport.birthYear<=2002)
def checkIssueYear(passport: Passport)->bool:
if passport.issueYear is None: return False
return (2010<=passport.issueYear<=2020)
def checkExpirationYear(passport: Passport)->bool:
if passport.expirationYear is None: return False
return (2020<=passport.expirationYear<=2030)
def checkHeight(passport: Passport)->bool:
if passport.height is None: return False
rematch = re.match(r'(\d+)((?:in)|(?:cm))', passport.height)
if rematch is None: return False
number = int(rematch.group(1))
unit = rematch.group(2)
if unit == 'in':
return (59<=number<=76)
else:
return (150<=number<=193)
def checkHairColour(passport: Passport)->bool:
if passport.hairColor is None: return False
return (re.match(r'#[0123456789abcdef]{6}$', passport.hairColor) is not None)
def checkEyeColour(passport: Passport)->bool:
if passport.eyeColor is None: return False
return (passport.eyeColor == 'amb'
or passport.eyeColor == 'blu'
or passport.eyeColor == 'brn'
or passport.eyeColor == 'gry'
or passport.eyeColor == 'grn'
or passport.eyeColor == 'hzl'
or passport.eyeColor == 'oth'
)
def checkPassportId(passport: Passport)->bool:
if passport.passportId is None: return False
return (re.match(r'[0-9]{9}$', passport.passportId) is not None)
def checkPassport(passport: Passport)->bool:
return (checkBirthYear(passport)
and checkIssueYear(passport)
and checkExpirationYear(passport)
and checkHeight(passport)
and checkHairColour(passport)
and checkEyeColour(passport)
and checkPassportId(passport)
)
def part2(data):
"""Solve part 2"""
return sum(1 for record in data if checkPassport(record))
def solve(puzzle_input):
"""Solve the puzzle for the given input"""
data = parse(puzzle_input)
solution1 = part1(data)
solution2 = part2(data)
return solution1, solution2
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"{path}:")
puzzle_input = pathlib.Path(path).read_text().strip()
solutions = solve(puzzle_input)
print("\n".join(str(solution) for solution in solutions))

13
day4/example1 Normal file
View file

@ -0,0 +1,13 @@
ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm
iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929
hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm
hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in

1146
day4/input Normal file

File diff suppressed because it is too large Load diff

65
day4/test_day4.py Normal file
View file

@ -0,0 +1,65 @@
#! /usr/bin/env python3
import pathlib
import pytest
import day4 as aoc
from day4 import Passport
PUZZLE_DIR = pathlib.Path(__file__).parent
#these test fixtures setup the test, mainly by reading the filename into a string in this simple case.
@pytest.fixture
def example1():
puzzle_input = (PUZZLE_DIR / "example1").read_text().strip()
return aoc.parse(puzzle_input)
@pytest.fixture
def example2():
puzzle_input = (PUZZLE_DIR / "example2").read_text().strip()
return aoc.parse(puzzle_input)
# @pytest.mark.skip(reason="Not implemented")
def test_parse_example1(example1):
"""Test that input is parsed properly"""
assert example1 == [Passport(birthYear=1937,
issueYear=2017,
expirationYear=2020,
height='183cm',
hairColor='#fffffd',
eyeColor='gry',
passportId='860033327',
countryId='147'),
Passport(birthYear=1929,
issueYear=2013,
expirationYear=2023,
height=None,
hairColor='#cfa07d',
eyeColor='amb',
passportId='028048884',
countryId='350'),
Passport(birthYear=1931,
issueYear=2013,
expirationYear=2024,
height='179cm',
hairColor='#ae17e1',
eyeColor='brn',
passportId='760753108',
countryId=None),
Passport(birthYear=None,
issueYear=2011,
expirationYear=2025,
height='59in',
hairColor='#cfa07d',
eyeColor='brn',
passportId='166559648',
countryId=None)]
# @pytest.mark.skip(reason="Not implemented")
def test_part1_example1(example1):
"""Test part 1 on example input"""
assert aoc.part1(example1) == 2
@pytest.mark.skip(reason="Not implemented")
def test_part2_example2(example2):
"""Test part 2 on example input"""
assert aoc.part2(example2) == ...

0
template.py Normal file → Executable file
View file