finished day 4
This commit is contained in:
		
							parent
							
								
									e6354cf097
								
							
						
					
					
						commit
						122e48ef5d
					
				
					 5 changed files with 1356 additions and 0 deletions
				
			
		
							
								
								
									
										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) == ...
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue