16_tickets.py 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. #!/usr/bin/env python3
  2. import sys
  3. from functools import reduce
  4. from operator import mul, or_
  5. def parse(f):
  6. rules = {}
  7. for line in f:
  8. if line == '\n':
  9. break
  10. field, ranges = line.rstrip().split(': ')
  11. rules[field] = valid = set()
  12. for rangestr in ranges.split(' or '):
  13. start, end = rangestr.split('-')
  14. valid |= set(range(int(start), int(end) + 1))
  15. assert next(f) == 'your ticket:\n'
  16. mine = tuple(map(int, next(f).split(',')))
  17. assert next(f) == '\n'
  18. assert next(f) == 'nearby tickets:\n'
  19. nearby = []
  20. for line in f:
  21. nearby.append(tuple(map(int, line.split(','))))
  22. return rules, mine, nearby
  23. def filter_valid(rules, tickets):
  24. valid = []
  25. error_rate = 0
  26. for ticket in tickets:
  27. errors = [field for field in ticket
  28. if not any(field in values for values in rules.values())]
  29. if errors:
  30. error_rate += sum(errors)
  31. else:
  32. valid.append(ticket)
  33. return error_rate, valid
  34. def field_order(rules, tickets):
  35. order = [set(rules) for field in tickets[0]]
  36. for ticket in tickets:
  37. for field, names in zip(ticket, order):
  38. names -= {n for n in names if field not in rules[n]}
  39. known = {names.pop(): i for i, names in enumerate(order) if len(names) == 1}
  40. while len(known) != len(tickets[0]):
  41. for i, names in enumerate(order):
  42. names -= set(known)
  43. if len(names) == 1:
  44. known[names.pop()] = i
  45. return [n for i, n in sorted((i, n) for n, i in known.items())]
  46. rules, mine, nearby = parse(sys.stdin)
  47. error_rate, valid = filter_valid(rules, nearby)
  48. print(error_rate)
  49. order = field_order(rules, [mine] + valid)
  50. departure = (mine[i] for i, n in enumerate(order) if n.startswith('departure'))
  51. print(reduce(mul, departure))