Day 8

So we've received a set of instructions that tell us which container to change, by how much, and under what condition. This seems like it would be a good chance to use the eval() funciton in R as you could just evaluate whether the statement is true or not and then procede with the instructions. As for python.... we'll see what we come up with.

In [5]:
# load the puzzle input, It seems like I want to put everything into a DataFrame but I guess that's how my work goes
import pandas as pd
import numpy as np

initial = open('/Users/Sven/py_files/aoc_2017/d8_input.txt')
initial = initial.readlines()
initial[:5]
Out[5]:
['uz inc 134 if hx > -10\n',
 'qin dec -300 if h <= 1\n',
 'ubi inc 720 if qin <= 306\n',
 'si inc -108 if he <= 1\n',
 'hx inc 278 if hx <= -10\n']
In [6]:
# I guess we'll do something very similar to last time to format the input
def fmt_block(txt):
    txt = txt.replace('\n', '')
    # split on spaces and then take pieces
    splits = txt.split()
    name = splits[0]
    direction = splits[1]
    ammt = int(splits[2])
    
    # now get the condition
    splits2 = txt.split('if ')[1].split()
    cond = splits2[1] + splits2[2]
    cond_var = splits2[0]
    
    # return the data frame:
    dat = pd.DataFrame({'name' : [name], 'direction' : [direction], 'ammt' : [ammt], 'cond_var' : [cond_var], 'cond' : [cond]})
    return(dat)

fmt_block(initial[0])
Out[6]:
name direction ammt cond_var cond
0 uz inc 134 hx >-10

Again, loop through the list of strings, turn into data frames, and bind together

In [7]:
frames = [fmt_block(x) for x in initial]
instructions = pd.concat(frames).reset_index(drop = True)

So this increasing decreasing thing is kinda silly, let's just turn them all into increases

In [8]:
def flipper(x):
    if x == 'inc':
        return(1)
    else:
        return(-1)
instructions['flipper'] = [flipper(x) for x in instructions.direction]
instructions['ammt2'] = instructions.flipper*instructions.ammt
display(instructions[:5])
name direction ammt cond_var cond flipper ammt2
0 uz inc 134 hx >-10 1 134
1 qin dec -300 h <=1 -1 300
2 ubi inc 720 qin <=306 1 720
3 si inc -108 he <=1 1 -108
4 hx inc 278 hx <=-10 1 278

I think it will be less cumbersome to keep track of the values as a dictionary, rather than additional columns within the instructions. I guess I'll not assume that the unique values in name are the only ones that could appear in the conditions so let's concatenate these.

In [9]:
labels = instructions.name.unique().tolist()
labels2 = instructions.cond_var.unique().tolist()
labels.sort()
labels2.sort()
print(labels == labels2)

# Okay I guess I didn't need to do that but whatever
zeroes = [0 for i in range(len(labels))]
containers = {key : value for (key, value) in zip(labels, zeroes)}
containers
True
Out[9]:
{'a': 0,
 'f': 0,
 'fkh': 0,
 'h': 0,
 'he': 0,
 'hx': 0,
 'jke': 0,
 'ke': 0,
 'nfi': 0,
 'o': 0,
 'qin': 0,
 'si': 0,
 'sy': 0,
 't': 0,
 'ty': 0,
 'u': 0,
 'ubi': 0,
 'uz': 0,
 'w': 0,
 'wg': 0,
 'xf': 0,
 'xp': 0,
 'yf': 0,
 'yu': 0,
 'z': 0,
 'zwq': 0,
 'zwx': 0}

Okay that was easier than expected. Now comes the hard part of figuring out how to evaluate these character strings within the data set. We'll have to do some kind of for loop downward through the rows because the validity of the statements will depend on the current state. I guess we'll just go back to our trick of making a function that works for a single case and iterate. This time I'm going to try and not work with global variables within the function though as it's not kosher.

In [10]:
def eval_cond(dictionary, i):
    
    # first get information from the instructions
    inst_cond = instructions.cond.iloc[i]
    inst_cond_var = instructions.cond_var.iloc[i]
    inst_name = instructions.name.iloc[i]
    inst_ammt = instructions.ammt2.iloc[i]
    
    # Now get the value from the dictionary we just received
    dict_cond_var = dictionary[inst_cond_var]
        
    # evaluate the condition and modify dict if true
    check = str(dict_cond_var) + inst_cond
    if eval(check):
        dictionary[inst_name] += inst_ammt
    return(dictionary)

Now we jsut run this through the length of the instructions

In [11]:
containers_copy = containers.copy()
for i in range(len(instructions)):
    containers_copy = eval_cond(containers_copy, i)
max(list(containers_copy.values()))
Out[11]:
4647

Part 2

We're given a slight wrinkle on the problem where we want to know the maximum value ever reached during the process. I think this should be pretty simple by just computing the max value after each function call.

In [12]:
m = 0
containers_copy2 = containers.copy()
for i in range(len(instructions)):
    containers_copy2 = eval_cond(containers_copy2, i)
    new_max = max(list(containers_copy2.values()))
    m = max([m, new_max])
m
Out[12]:
5590