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) == ...
 | 
				
			||||||
							
								
								
									
										0
									
								
								template.py
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								template.py
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue