diff --git a/src/Year_2015/P11.py b/src/Year_2015/P11.py index 949fa48..6205cc0 100644 --- a/src/Year_2015/P11.py +++ b/src/Year_2015/P11.py @@ -39,60 +39,67 @@ # Given Santa's current password (your puzzle input), what should his next # password be? -import re -from string import ascii_lowercase -with open("files/P11.txt", "r") as f: - password = [c for c in f.read().strip()] +with open("files/P11.txt") as f: + password = f.read().strip() -forbidden_chars = {105, 108, 111} +FORBIDDEN_CHARS = {"i", "o", "l"} +FORBIDDEN_ASCII = {ord(char) for char in FORBIDDEN_CHARS} -def rule_1(chars: list[str]) -> bool: - # increasing straight of at least three letters +def rule_1(password: list[str]) -> bool: return any( - "".join(chars[i : i + 3]) in ascii_lowercase - for i in range(len(chars) - 2) + ord(password[i]) + 1 == ord(password[i + 1]) + and ord(password[i + 1]) + 1 == ord(password[i + 2]) + for i in range(len(password) - 2) ) -def rule_2(chars: list[str], pos: int) -> list[str]: - # try with next letter - a = ord(chars[pos]) + 1 - # not contain the letters i, o, or l - if a in forbidden_chars: - a += 1 - # wraps around to a - if a > 122: - a = 97 - chars[pos] = chr(a) - # change now previous letter also - rule_2(chars, pos - 1) - else: - chars[pos] = chr(a) - return chars +def rule_2(password: list[str]) -> bool: + return not any(char in FORBIDDEN_CHARS for char in password) -def rule_3(chars: list[str]) -> bool: - # at least two different, non-overlapping pairs of letters - return bool(re.search(r"(\w)\1.*(\w)\2", "".join(chars))) +def rule_3(password: list[str]) -> bool: + pairs = [] + index = 0 + while index < len(password) - 1: + if password[index] == password[index + 1]: + pairs.append(password[index]) + index += 2 + else: + index += 1 + return len(set(pairs)) >= 2 -def part_1() -> None: - new_password = password - found = False - while not found: - new_password = rule_2(new_password, -1) - # rule 1 - if not rule_1(new_password): - continue - # rule # 3 - if not rule_3(new_password): +def is_valid_password(password: list[str]) -> bool: + return rule_1(password) and rule_2(password) and rule_3(password) + + +def increment_password(password: list[str]) -> list[str]: + updated = password.copy() + + for index in range(len(updated) - 1, -1, -1): + next_value = ord(updated[index]) + 1 + + while next_value in FORBIDDEN_ASCII: + next_value += 1 + + if next_value > ord("z"): + updated[index] = "a" continue - res = "".join(new_password) - print(f"Santa's next password is {res}") - found = True + updated[index] = chr(next_value) + return updated + + raise ValueError("Password overflowed") + + +def guess_password(start_password: str) -> str: + password = list(start_password) + while True: + password = increment_password(password) + if is_valid_password(password): + return "".join(password) # --- Part Two --- @@ -101,6 +108,9 @@ def part_1() -> None: if __name__ == "__main__": - part_1() + first_password = guess_password(password) + print(f"Santa's next password is {first_password}") + # Same code again - part_1() + second_password = guess_password(first_password) + print(f"Santa's next password is {second_password}")