<reponame>rluetzner/advent-of-code-2020<filename>day-16/aoc-day16.py
#!/usr/bin/env python

import sys
sys.path.append('..')
from libs.helpers import get_input
import re
import numpy as np


def parse_rules(lines):
    rules = {}
    for l in lines:
        if not l:
            # exit on first empty line
            break
        m = re.search(r'^([a-z ]+): (\d+-\d+(?: or )?)+(\d+-\d+)$', l)
        if m:
            g = m.groups()
            rules[g[0]] = [[int (n)for n in r.replace(' or ','').split('-')] for r in g[1:]]
    return rules


def get_ticket(lines):
    for i in range(len(lines)):
        if lines[i] == 'your ticket:':
            return [int(n) for n in lines[i+1].split(',')]
    raise(ValueError('Did not find your ticket'))


def get_others(lines):
    for i in range(len(lines)):
        if lines[i] == 'nearby tickets:':
            return [[int(n) for n in l.split(',')] for l in lines[i+1:]]
    raise(ValueError('Did not find nearby tickets'))


def invalid_score(rules, others):
    nums = [n for lst in others for n in lst]
    res = 0
    for n in nums:
        if not is_valid(rules, n):
            res += n
    return res

def is_valid(rules, n):
    for r in rules:
        for v in rules[r]:
            mi,ma = v
            if n in range(mi, ma+1):
                return True
    return False


def only_valid_tickets(rules, others):
    return list(filter(lambda x: all(is_valid(rules, n) for n in x), others))


def match_fields(rules, others):
    poss = {r: [] for r in rules}

    for i in range(len(others)):
        values = others[i]
        for r in rules:
            if all(is_valid({r:rules[r]}, n) for n in values):
                poss[r].append(i)
    unmatched = [r for r in poss]
    while unmatched:
        obvious = list(filter(lambda x: len(poss[x]) == 1, unmatched))
        fields = [i for o in obvious for i in poss[o]]
        for r in poss:
            if r in obvious:
                if r in unmatched:
                    unmatched.remove(r)
                continue
            for i in fields:
                if i in poss[r]:
                    poss[r].remove(i)
    return {poss[p][0]: p for p in poss.keys()}



lines = get_input('input.txt')
rules = parse_rules(lines)
ticket = get_ticket(lines)
others = get_others(lines)
res = invalid_score(rules, others)
print(res)
others = only_valid_tickets(rules, others)
others = np.array(others).transpose()
field_idx_map = match_fields(rules, others)
print(field_idx_map)
mapped_ticket = {field_idx_map[i]: ticket[i] for i in range(len(ticket))}
print(mapped_ticket)
res = 1
for m in filter(lambda k: 'departure' in k, mapped_ticket.keys()):
    res *= mapped_ticket[m]
print(res)

#!/usr/bin/env python

import sys
sys.path.append('..')
from libs.helpers import get_input
import re
import numpy as np


def parse_rules(lines):
    rules = {}
    for l in lines:
        if not l:
            # exit on first empty line
            break
        m = re.search(r'^([a-z ]+): (\d+-\d+(?: or )?)+(\d+-\d+)$', l)
        if m:
            g = m.groups()
            rules[g[0]] = [[int (n)for n in r.replace(' or ','').split('-')] for r in g[1:]]
    return rules


def get_ticket(lines):
    for i in range(len(lines)):
        if lines[i] == 'your ticket:':
            return [int(n) for n in lines[i+1].split(',')]
    raise(ValueError('Did not find your ticket'))


def get_others(lines):
    for i in range(len(lines)):
        if lines[i] == 'nearby tickets:':
            return [[int(n) for n in l.split(',')] for l in lines[i+1:]]
    raise(ValueError('Did not find nearby tickets'))


def invalid_score(rules, others):
    nums = [n for lst in others for n in lst]
    res = 0
    for n in nums:
        if not is_valid(rules, n):
            res += n
    return res

def is_valid(rules, n):
    for r in rules:
        for v in rules[r]:
            mi,ma = v
            if n in range(mi, ma+1):
                return True
    return False


def only_valid_tickets(rules, others):
    return list(filter(lambda x: all(is_valid(rules, n) for n in x), others))


def match_fields(rules, others):
    poss = {r: [] for r in rules}

    for i in range(len(others)):
        values = others[i]
        for r in rules:
            if all(is_valid({r:rules[r]}, n) for n in values):
                poss[r].append(i)
    unmatched = [r for r in poss]
    while unmatched:
        obvious = list(filter(lambda x: len(poss[x]) == 1, unmatched))
        fields = [i for o in obvious for i in poss[o]]
        for r in poss:
            if r in obvious:
                if r in unmatched:
                    unmatched.remove(r)
                continue
            for i in fields:
                if i in poss[r]:
                    poss[r].remove(i)
    return {poss[p][0]: p for p in poss.keys()}



lines = get_input('input.txt')
rules = parse_rules(lines)
ticket = get_ticket(lines)
others = get_others(lines)
res = invalid_score(rules, others)
print(res)
others = only_valid_tickets(rules, others)
others = np.array(others).transpose()
field_idx_map = match_fields(rules, others)
print(field_idx_map)
mapped_ticket = {field_idx_map[i]: ticket[i] for i in range(len(ticket))}
print(mapped_ticket)
res = 1
for m in filter(lambda k: 'departure' in k, mapped_ticket.keys()):
    res *= mapped_ticket[m]
print(res)
#!/usr/bin/env python # exit on first empty line