48 lines
1.4 KiB
Python
48 lines
1.4 KiB
Python
import math
|
|
from dataclasses import dataclass
|
|
from typing import Iterator
|
|
|
|
|
|
@dataclass
|
|
class Observation:
|
|
keypad: list[list[int]]
|
|
key_selection: list[int]
|
|
|
|
@property
|
|
def property_list(self) -> list[set[int]]:
|
|
return [set(self.keypad[idx]) for idx in self.key_selection]
|
|
|
|
|
|
@dataclass
|
|
class EvilOutput:
|
|
possible_nkodes: list[list[int]]
|
|
iterations: int
|
|
|
|
@property
|
|
def number_of_possible_nkode(self):
|
|
return math.prod([len(el) for el in self.possible_nkodes])
|
|
|
|
|
|
@dataclass
|
|
class Evilkode:
|
|
observations: Iterator[Observation]
|
|
passcode_len: int
|
|
number_of_keys: int
|
|
properties_per_key: int
|
|
max_tries_before_lockout: int = 5
|
|
possible_nkode = None
|
|
|
|
|
|
def initialize(self):
|
|
possible_values = set(range(self.number_of_keys * self.properties_per_key))
|
|
self.possible_nkode = [possible_values.copy() for _ in range(self.passcode_len)]
|
|
|
|
def run(self) -> EvilOutput:
|
|
self.initialize()
|
|
for idx, obs in enumerate(self.observations):
|
|
if math.prod([len(el) for el in self.possible_nkode]) <= self.max_tries_before_lockout:
|
|
return EvilOutput(possible_nkodes=[list(el) for el in self.possible_nkode], iterations=idx+1)
|
|
for jdx, props in enumerate(obs.property_list):
|
|
self.possible_nkode[jdx] = props.intersection(self.possible_nkode[jdx])
|
|
raise Exception("error in Evilkode, observations stopped yielding")
|