finished day 4
This commit is contained in:
parent
e6354cf097
commit
122e48ef5d
132
day4/day4.py
Executable file
132
day4/day4.py
Executable 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
13
day4/example1
Normal 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
1146
day4/input
Normal file
File diff suppressed because it is too large
Load diff
65
day4/test_day4.py
Normal file
65
day4/test_day4.py
Normal 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
0
template.py
Normal file → Executable file
Loading…
Reference in a new issue