summaryrefslogtreecommitdiffstats
path: root/20/py/d16.py
blob: 09a7c9cbfa5b640feadad217ee8e4f7883dc4e67 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env python3
import aoc20
import sys


def pt1(_in):
    constraints = [[range(int((b := a.split("-"))[0]), int(b[1])+1)
                    for a in line.split(": ")[1].split(" or ")]
                   for line in _in[:20]]
    return sum(sum(int(field)
                   for field in line.strip().split(",")
                   if not any(any(int(field) in cons
                                  for cons in constraint)
                              for constraint in constraints))
               for line in _in[25:])


def pt2(_in):
    constraints = [[range(int((b := a.split("-"))[0]), int(b[1])+1)
                    for a in line.split(": ")[1].split(" or ")]
                   for line in _in[:20]]

    # all(any(any())) => ALL numbers on a ticket match ANY constraint in ANY constraint group
    tickets = [[int(n) for n in line.strip().split(",")]
               for line in _in[25:]
               if all(any(any(int(n) in cons
                              for cons in constraint)
                          for constraint in constraints)
                      for n in line.strip().split(","))]

    # for each field
    #   assume it can be anything
    candidates = [set(range(20)) for _ in range(20)]  # one set per field

    for ticket in tickets:
        for f, field in enumerate(ticket):
            for c, constraint in enumerate(constraints):
                if c in candidates[f] and not any(field in cons for cons in constraint):
                    candidates[f].remove(c)

    know = [-1 for _ in range(20)]
    while any(len(c) > 0 for c in candidates):
        for c, cand in enumerate(candidates):
            if len(cand) == 1:
                know[c] = cand.pop()
                to_remove = know[c]
                break
        for cand in candidates:
            if to_remove in cand:
                cand.remove(to_remove)

    my = list(int(n) for n in _in[22].strip().split(","))
    res = 1
    for f, field in enumerate(know):
        if field < 6:
            res *= my[f]
    return res


if __name__ == "__main__":
    input = aoc20.read_input(sys.argv[1:], 16)
    print(pt1(input))
    print(pt2(input))