Because the digits must be the same or increase, the number of combinations is fairly limited. The total number of such 'passwords' is a 6-simplex polytopic numbers, the value of which can be calculated as
$$\dbinom{10 + 6 - 1}{6} = \dbinom{15}{6} = 5005$$(where 10
is the number of digits and 6
the number of dimensions). We can easily brute-force this by generating all the possible combinations of increasing digits, using a recursive loop over a digits string (which we shorten for the next recursive call to ensure digits only increase).
The passwords can then be checked to be within the low-high value range, and the number of unique digits needs to be less than 6 for digits to have repeated.
from __future__ import annotations
from typing import Callable, Iterable
Checker = Callable[[int], bool]
def produce(n: int = 6, digits: str = "0123456789") -> Iterable[str]:
if n == 0:
yield ""
return
for i, d in enumerate(digits):
for remainder in produce(n - 1, digits[i:]):
yield d + remainder
def password_checker_factory(lo: int, hi: int) -> Checker:
def is_valid(pw: int) -> bool:
return (lo <= pw <= hi) and len(set(str(pw))) < 6
return is_valid
def count_valid(checker: Checker) -> int:
return sum(1 for _ in filter(checker, map(int, produce())))
tests = {
(111111, 111111): 1,
(223450, 223450): 0,
(123789, 123789): 0,
}
for (lo, hi), expected in tests.items():
assert count_valid(password_checker_factory(lo, hi)) == expected
import aocd
lo, hi = map(int, aocd.get_data(day=4, year=2019).strip().split("-"))
print("Part 1:", count_valid(password_checker_factory(lo, hi)))
Part 1: 1764
This is just a slightly stricter checker. Instead of the number of unique digits, we need to count consecutive digits and assert there is a group of length 2. This is a job for itertools.groupby()
!
from itertools import groupby
def stricter_password_checker_factory(lo: int, hi: int) -> Checker:
def has_2_adjacent(pw: int):
return any(sum(1 for _ in g) == 2 for _, g in groupby(str(pw)))
def is_valid(pw: int):
return (lo <= pw <= hi) and has_2_adjacent(pw)
return is_valid
strict_tests = {112233: True, 123444: False, 111122: True}
for pw, expected in strict_tests.items():
assert stricter_password_checker_factory(pw, pw)(pw) == expected
print("Part 2:", count_valid(stricter_password_checker_factory(lo, hi)))
Part 2: 1196