Skip to content

Instantly share code, notes, and snippets.

@mjpieters
Last active December 23, 2015 18:14
Show Gist options
  • Save mjpieters/1566689dc72ab4055a40 to your computer and use it in GitHub Desktop.
Save mjpieters/1566689dc72ab4055a40 to your computer and use it in GitHub Desktop.
Advent of Code fun
# Having fun with http://adventofcode.com/day/6 Advent of Code
import re
from itertools import product
from collections import defaultdict
try:
from PIL import Image
except ImportError:
Image = None
class Grid(object):
_line_pat = re.compile(
r'(?:(?:turn (on|off))|(toggle)) (\d+),(\d+) through (\d+),(\d+)')
def __init__(self):
self._lights = set()
def __len__(self):
return len(self._lights)
def _gen_grid(self, x1, y1, x2, y2):
return product(range(x1, x2 + 1), range(y1, y2 + 1))
def process_line(self, line):
match = self._line_pat.search(line)
if not match:
return
state, toggle = match.group(1, 2)
x1, y1, x2, y2 = map(int, match.group(3, 4, 5, 6))
if state == 'on':
self._lights.update(self._gen_grid(x1, y1, x2, y2))
elif state == 'off':
self._lights.difference_update(self._gen_grid(x1, y1, x2, y2))
else:
grid = set(self._gen_grid(x1, y1, x2, y2))
switch_on = grid - self._lights
self._lights -= grid & self._lights
self._lights |= switch_on
# tests
g = Grid()
g.process_line('turn on 0,0 through 999,999')
assert len(g) == 1000000
g = Grid()
g.process_line('toggle 0,0 through 999,0')
assert len(g) == 1000
g = Grid()
g.process_line('turn on 0,0 through 499,0')
g.process_line('toggle 0,0 through 999,0')
assert len(g) == 500
g = Grid()
g.process_line('turn on 499,0 through 500,999')
g.process_line('turn off 499,499 through 500,500')
assert len(g) == (2000 - 4)
class BrightnessGrid(Grid):
def __init__(self):
self._lights = [[0 for _ in range(1000)] for _ in range(1000)]
self._brightness = 0
def __len__(self):
return self._brightness
def process_line(self, line):
match = self._line_pat.search(line)
if not match:
return
state, toggle = match.group(1, 2)
x1, y1, x2, y2 = map(int, match.group(3, 4, 5, 6))
lights = self._lights
coords = self._gen_grid(x1, y1, x2, y2)
if state == 'on' or toggle:
amount = 2 if toggle else 1
for x, y in coords:
lights[x][y] += amount
self._brightness += amount
else:
for x, y in coords:
if lights[x][y] > 0:
lights[x][y] -= 1
self._brightness -= 1
g = BrightnessGrid()
g.process_line('turn on 0,0 through 0,0')
assert len(g) == 1
g.process_line('toggle 0,0 through 999,999')
assert len(g) == 2000001
g.process_line('turn off 0,0 through 999,999')
assert len(g) == 1000001
g.process_line('turn off 0,0 through 999,999')
assert len(g) == 1
g.process_line('turn off 0,0 through 999,999')
assert len(g) == 0
if Image is not None:
class ImageGrid(Grid):
def __init__(self):
self._lights = Image.new('L', (1000, 1000), 0)
def process_line(self, line):
match = self._line_pat.search(line)
if not match:
return
state, toggle = match.group(1, 2)
box = [int(c) for c in match.group(3, 4, 5, 6)]
lights = self._lights
region = lights.crop(box)
if state == 'on':
region = region.point(lambda p: 255)
elif state == 'off':
region = region.point(lambda p: 0)
else:
region = region.point(lambda p: 255 - p)
lights.paste(region, box)
def save(self, filename):
self._lights.save(filename)
class BrightnessImageGrid(ImageGrid, BrightnessGrid):
def process_line(self, line):
match = self._line_pat.search(line)
if not match:
return
state, toggle = match.group(1, 2)
box = [int(c) for c in match.group(3, 4, 5, 6)]
lights = self._lights
region = lights.crop(box)
if state == 'on' or toggle:
amount = 10 if toggle else 5
region = region.point(lambda p: p + amount)
else:
region = region.point(lambda p: max(0, p - 5))
lights.paste(region, box)
if __name__ == '__main__':
import sys
import os.path
filename = sys.argv[-1]
if '-1' in sys.argv:
g = Grid()
with open(filename) as f:
for line in f:
g.process_line(line)
print('First part:', len(g))
if '-2' in sys.argv:
bg = BrightnessGrid()
with open(filename) as f:
for line in f:
bg.process_line(line)
print('Second part:', len(bg))
if Image is not None:
if '-img1' in sys.argv:
output = os.path.join(
os.path.dirname(filename),
os.path.splitext(os.path.basename(filename))[0] + '_img1.png')
ig = ImageGrid()
with open(filename) as f:
for line in f:
ig.process_line(line)
ig.save(output)
print('Saved', output)
if '-img2' in sys.argv:
output = os.path.join(
os.path.dirname(filename),
os.path.splitext(os.path.basename(filename))[0] + '_img2.png')
big = BrightnessImageGrid()
with open(filename) as f:
for line in f:
big.process_line(line)
big.save(output)
print('Saved', output)
import operator
import re
class Expression(object):
_ops = {
'AND': operator.and_,
'OR': operator.or_,
'NOT': lambda x, y: ~y & 0xFFFF, # bitwise inversion, only one operand
'LSHIFT': lambda x, y: (x << y) & 0xFFFF,
'RSHIFT': operator.rshift,
None: lambda x, y: x, # direct assignment, only one operand
}
_line = re.compile(
r'(?:(?P<op1>[a-z]+|\d+) )?(?:(?P<operator>{}) (?P<op2>[a-z]+|\d+) )?'
r'-> (?P<target>[a-z]+)'.format(
'|'.join([k for k in _ops if k is not None])))
def __init__(self, op1, operator, op2, target):
self.op1 = int(op1) if op1 and op1.isdigit() else op1
self.op2 = int(op2) if op2 and op2.isdigit() else op2
self.op_name = operator
self.operator = self._ops[operator]
self.target = target
@classmethod
def from_line(cls, line):
match = cls._line.search(line)
if not match:
raise ValueError('not a valid expression')
return cls(**match.groupdict())
def __repr__(self):
return (
'{}("{s.op1!r} {s.op_name!r} {s.op2!r} -> {s.target!r}")'.format(
type(self).__name__, s=self))
def __call__(self, expressions, _cache):
if self.target in _cache:
return _cache[self.target]
op1, op2 = (
expressions[op](expressions, _cache) if isinstance(op, str) else op
for op in (self.op1, self.op2))
res = _cache[self.target] = self.operator(op1, op2)
return res
class Solver(object):
def __init__(self):
# target -> expression. All targets are unique
self.expressions = {}
def parse_instruction(self, line):
expr = Expression.from_line(line)
self.expressions[expr.target] = expr
def solve(self, target='a'):
return self.expressions[target](self.expressions, {})
def to_dot_graph(self, output):
with open(output, 'w') as df:
df.write('digraph advent_expression {\n')
for node in self.expressions.values():
df.write(
'n{0} [shape=box,label="{0}"]\n'
'n{0}_expr [shape=plaintext,label="{1}"]\n'
'n{0} -> n{0}_expr\n'.format(
node.target, node.op_name or '='))
for label, op in (('l', node.op1), ('r', node.op2)):
if op is None:
continue
if isinstance(op, int):
# Numbers are repeated
df.write(
'n{0}{1} [shape=circle,label="{1}"]\n'
'n{0}_expr -> n{0}{1} [label="{2}"]\n'.format(
node.target, op, label))
else:
df.write('n{0}_expr -> n{1} [label="{2}"]\n'.format(
node.target, op, label))
df.write('}\n')
if __name__ == '__main__':
import sys
import os.path
filename = sys.argv[-1]
solver = Solver()
with open(filename) as inputfile:
for line in inputfile:
solver.parse_instruction(line)
result = solver.solve()
print('Part 1:', result)
if '--graph' in sys.argv:
dirname, base = os.path.split(filename)
output = os.path.join(
dirname, os.path.splitext(base)[0] + '.dot')
solver.to_dot_graph(output)
print('Saved graph to', output)
solver.expressions['b'].op1 = result
print('Part 2:', solver.solve())
import os
from ast import literal_eval
here = os.path.dirname(os.path.abspath(__file__))
filename = os.path.join(here, 'inputs/input8.txt')
# quick and dirty way; have Python do the parsing
diff1 = diff2 = 0
for l in open(filename):
diff1 += len(l) - len(literal_eval(l))
diff2 += 2 + l.count('"') + l.count('\\')
print('Part 1:', diff1)
print('Part 2:', diff2)
# Travelling salesm^WSanta.
# Just bruteforce it; it's only 8! comparisons
import re
from itertools import permutations
def shortest(g):
return min(
sum(g[a][b] for a, b in zip(path, path[1:]))
for path in permutations(g))
def longest(g):
return max(
sum(g[a][b] for a, b in zip(path, path[1:]))
for path in permutations(g))
def read_graph(fileobj):
parse_line = re.compile(r'(\w+) to (\w+) = (\d+)')
g = {}
for line in fileobj:
m = parse_line.search(line)
if not m:
continue
from_, to, dist = m.groups()
g.setdefault(from_, {})[to] = int(dist)
g.setdefault(to, {})[from_] = int(dist)
return g
if __name__ == '__main__':
import sys
filename = sys.argv[1]
with open(filename) as f:
g = read_graph(f)
print('Part 1:', shortest(g))
print('Part 2:', longest(g))
# --- Day 10: Elves Look, Elves Say ---
from itertools import groupby
from functools import reduce
def looksay(digits):
return ''.join(['{}{}'.format(len(list(group)), d)
for d, group in groupby(digits)])
assert looksay('1') == '11'
assert looksay('11') == '21'
assert looksay('21') == '1211'
assert looksay('1211') == '111221'
assert looksay('111221') == '312211'
if __name__ == '__main__':
res1 = reduce(lambda prev, i: looksay(prev), range(40), '1321131112')
print('Part 1:', len(res1))
# iterate an *additional* 10 times
res2 = reduce(lambda prev, i: looksay(prev), range(10), res1)
print('Part 2:', len(res2))
from string import ascii_lowercase
from itertools import chain, permutations, product
import bisect
import re
import time
letters = ''.join([l for l in ascii_lowercase if l not in 'ilo'])
# 21 valid 3-letter consecutive sequences
sequences = [''.join(seq)
for seq in zip(
ascii_lowercase, ascii_lowercase[1:], ascii_lowercase[2:])
if not any(c in 'ilo' for c in seq)]
def generate_next(password):
# no z, this is deliberate! The KeyError signals we need to recurse
next_map = {a: b for a, b in zip(letters, letters[1:])}
# Technically speaking, we would need to account for i, l and o too,
# since Santa may have used those in the old password
next_map.update({'i': 'j', 'l': 'm', 'o': 'p'})
def increment(pw):
try:
return pw[:-1] + next_map[pw[-1]]
except KeyError:
return increment(pw[:-1]) + 'a'
has_seq = re.compile(r'({})'.format('|'.join(sequences)))
has_doubles = re.compile(r'(.)\1.*(.)\2')
def isvalid(pw):
return bool(has_seq.search(pw) and has_doubles.search(pw))
while True:
password = increment(password)
if isvalid(password):
return password
def generate_all():
# There are only 15 ^ 5 possible 3-character consequtive sequences in the
# passwords. Combined with the requirement there are 2 doubled letter
# pairs, limits the total to 6712592 (6.7 million) different passwords
doubled = [l + l for l in letters]
print('Generating all possible passwords')
start = time.time()
all_passwords = sorted(set(chain(
# no overlap between sequence and doubled letters
(''.join(c)
for p in product(sequences, letters, doubled, doubled)
for c in permutations(p)),
# last letter of sequence doubled
(''.join(c)
for p in product((s + s[-1] for s in sequences),
doubled, *[letters] * 2)
for c in permutations(p)),
# first letter of sequence doubled
(''.join(c)
for p in product((s[0] + s for s in sequences),
doubled, *[letters] * 2)
for c in permutations(p)),
# first and last letter of sequence doubled
(''.join(c)
for p in product((s[0] + s + s[-1] for s in sequences),
*[letters] * 3)
for c in permutations(p)),
)))
print('Done generating all {} possible passwords in {:.3f} seconds'.format(
len(all_passwords), time.time() - start))
return all_passwords
if __name__ == '__main__':
import sys
old_password = sys.argv[-1]
if '--gen' in sys.argv:
all_passwords = generate_all()
new_password = all_passwords[
bisect.bisect_right(all_passwords, old_password)]
print('Part 1:', new_password)
next_password = all_passwords[
bisect.bisect_right(all_passwords, new_password)]
print('Part 2:', next_password)
else:
new_password = generate_next(old_password)
print('Part 1:', new_password)
next_password = generate_next(new_password)
print('Part 2:', next_password)
from itertools import islice, permutations
import re
def circular_window(seq, n=2):
it = iter(seq + seq[:n - 1])
result = tuple(islice(it, n))
yield result
for elem in it:
result = result[1:] + (elem,)
yield result
def calculate_happiness_change(graph, order):
return sum(graph[m][l] + graph[m][r]
for l, m, r in circular_window(order, 3))
def find_max_happiness_change(graph):
return max(calculate_happiness_change(graph, o)
for o in permutations(graph))
def read_input(fileobj):
linepattern = re.compile(
r'(\w+) would (gain|lose) (\d+) happiness units '
r'by sitting next to (\w+).')
graph = {}
for line in fileobj:
match = linepattern.search(line)
if not match:
continue
name1, direction, change, name2 = match.groups()
change = int(change) if direction == 'gain' else -int(change)
graph.setdefault(name1, {})[name2] = change
return graph
if __name__ == '__main__':
import os.path
import sys
filename = sys.argv[-1]
with open(filename) as inf:
graph = read_input(inf)
print('Part 1:', find_max_happiness_change(graph))
for name in graph:
graph[name]['Myself'] = 0
graph['Myself'] = dict.fromkeys(graph, 0)
print('Part 2:', find_max_happiness_change(graph))
if '--graph' in sys.argv:
dirname, basename = os.path.split(filename)
output = os.path.join(dirname, os.path.splitext(basename)[0] + '.dot')
order = max((o for o in permutations(graph)),
key=lambda o: calculate_happiness_change(graph, o))
with open(output, 'w') as df:
df.write('graph advent_seating {\nlayout="circo";\n')
for name, toright in circular_window(order, 2):
df.write(
'n{0} [shape=circle, label="{0}", width=1]\n'
'n{0} -- n{1} '
'[headlabel="{2}", taillabel="{3}", '
'labeldistance=2];\n'.format(
name, toright,
graph[name][toright], graph[toright][name]))
df.write('}\n')
print('Written graph to', output)
import re
class Reindeer(object):
def __init__(self, name, speed, travel_time, rest_time):
self.name = name
self.speed = speed
self.travel_time = travel_time
self.rest_time = rest_time
self.reset()
def __eq__(self, other):
if not isinstance(other, Reindeer):
return NotImplemented
return self.name == other.name
def __hash__(self):
return hash(self.name)
def distance_travelled(self, total_time):
periods = total_time // (self.travel_time + self.rest_time)
travelled = min(
total_time - (self.travel_time + self.rest_time) * periods,
self.travel_time) + periods * self.travel_time
return self.speed * travelled
def reset(self):
self.distance = 0
self.score = 0
self.running = True
self.duration = self.travel_time
def tick(self):
if self.running:
self.distance += self.speed
self.duration -= 1
if self.duration == 0:
self.running = not self.running
self.duration = (
self.travel_time if self.running else self.rest_time)
def run_race(reindeer, race_duration):
furthest_distance = 0
reindeer_in_lead = set()
for second in range(race_duration):
for racing_reindeer in reindeer:
racing_reindeer.tick()
if racing_reindeer.distance > furthest_distance:
furthest_distance = racing_reindeer.distance
reindeer_in_lead = {racing_reindeer}
elif racing_reindeer.distance == furthest_distance:
reindeer_in_lead.add(racing_reindeer)
for leading_reindeer in reindeer_in_lead:
leading_reindeer.score += 1
return max(r.score for r in reindeer)
def read_file(fileobj):
line_pattern = re.compile(
r'(\w+) can fly (\d+) km/s for (\d+) seconds, '
r'but then must rest for (\d+) seconds.')
return [
Reindeer(name, int(speed), int(tt), int(rt))
for line in fileobj if line.strip()
for name, speed, tt, rt in (line_pattern.search(line).groups(),)
]
if __name__ == '__main__':
import sys
filename = sys.argv[-2]
race_duration = int(sys.argv[-1])
with open(filename) as inf:
reindeer = read_file(inf)
print('Part 1:', max(r.distance_travelled(race_duration)
for r in reindeer))
print('Part 2:', run_race(reindeer, race_duration))
from collections import namedtuple
from functools import reduce
from itertools import product
from operator import mul
Ingredient = namedtuple(
'Ingredient', 'capacity durability flavor texture calories')
def read_ingredients(fileobj):
results = {}
for line in fileobj:
name, __, properties = line.strip().partition(': ')
properties = {name: int(val)
for prop in properties.split(',')
for name, val in (prop.split(),)}
results[name] = Ingredient(**properties)
return results
def optimise_for_score(ingredients, target_calories=None):
max_score = 0
for combo in product(range(100), repeat=len(ingredients) - 1):
if sum(combo) > 100:
continue
combo += ((100 - sum(combo)),)
if target_calories is not None:
calories = sum(ingr.calories * factor
for factor, ingr in zip(
combo, ingredients.values()))
if calories != target_calories:
continue
scores = [sum(ingr[p] * factor
for factor, ingr in zip(combo, ingredients.values()))
for p in range(4)]
if any(s <= 0 for s in scores):
continue
score = reduce(mul, scores)
if score > max_score:
max_score = score
return max_score
if __name__ == '__main__':
import sys
filename = sys.argv[-1]
with open(filename) as ingrf:
ingr = read_ingredients(ingrf)
score = optimise_for_score(ingr)
print('Part 1:', score)
score = optimise_for_score(ingr, 500)
print('Part 2:', score)
import re
from itertools import takewhile
from random import shuffle
def one_step_substitute(molecule, replacements):
for i, atom in enumerate(molecule):
for subst in replacements.get(atom, []):
yield ''.join(molecule[:i] + [subst] + molecule[i + 1:])
def random_replace(molecule, replacements):
# to hell with finding the right order of substitutions by analysis;
# there is only one solution and the *quickest* way to that solution
# is trying out random substition orderings.
molecule = ''.join(molecule)
reversed = [(v, k) for k in replacements for v in replacements[k]]
steps = 0
target = molecule
while target != 'e':
changed = False
for repl, source in reversed:
if repl in target:
target = target.replace(repl, source, 1)
steps += 1
changed = True
if not changed:
target = molecule
shuffle(reversed)
steps = 0
return steps
def read_input(fileobj, _atom=re.compile('[A-Z][a-z]*')):
fileobj = iter(fileobj)
replacements = {}
for line in takewhile(lambda l: l.strip(), fileobj):
source, __, target = line.strip().partition(' => ')
replacements.setdefault(source, []).append(target)
molecule = next(fileobj).strip()
return replacements, _atom.findall(molecule)
if __name__ == '__main__':
import sys
filename = sys.argv[-1]
with open(filename) as f:
replacements, molecule = read_input(f)
count = len(set(one_step_substitute(molecule, replacements)))
print('Part 1:', count)
steps = random_replace(molecule, replacements)
print('Part 2:', steps)
from itertools import product, combinations
from collections import namedtuple
import re
Item = namedtuple('Item', 'name cost damage armour')
conv = lambda n: int(n) if n.isdigit() else n
load = lambda lines: [Item(*map(conv, re.split(r'\s{2,}', l))) for l in lines]
weapons = load('''\
Dagger 8 4 0
Shortsword 10 5 0
Warhammer 25 6 0
Longsword 40 7 0
Greataxe 74 8 0
'''.splitlines())
# Armour is optional, include a noop.
armour = load('''\
Leather 13 0 1
Chainmail 31 0 2
Splintmail 53 0 3
Bandedmail 75 0 4
Platemail 102 0 5
None 0 0 0
'''.splitlines())
# Rings are optional; we pick between 0 and 2 so include 2 noops.
rings = load('''\
Damage +1 25 1 0
Damage +2 50 2 0
Damage +3 100 3 0
Defense +1 20 0 1
Defense +2 40 0 2
Defense +3 80 0 3
None 0 0 0
None 0 0 0
'''.splitlines())
def attack(weapon, rings, boss_armour):
return max(1, weapon.damage + sum(r.damage for r in rings) - boss_armour)
def defence(armour, rings, boss_attack):
return max(1, boss_attack - armour.armour - sum(r.armour for r in rings))
if __name__ == '__main__':
import sys
filename = sys.argv[-1]
with open(filename) as f:
boss_hp = int(next(f).rpartition(':')[-1])
boss_attack = int(next(f).rpartition(':')[-1])
boss_armour = int(next(f).rpartition(':')[-1])
player_hp = 100
min_cost = min(
weapon.cost + armour.cost + sum(r.cost for r in rings)
for weapon, armour, rings in product(
weapons, armour, combinations(rings, 2))
if (boss_hp // attack(weapon, rings, boss_armour) <=
player_hp // defence(armour, rings, boss_attack)))
print('Part 1:', min_cost)
max_cost = max(
weapon.cost + armour.cost + sum(r.cost for r in rings)
for weapon, armour, rings in product(
weapons, armour, combinations(rings, 2))
if (boss_hp // attack(weapon, rings, boss_armour) >
player_hp // defence(armour, rings, boss_attack)))
print('Part 2:', max_cost)
from collections import namedtuple
from functools import reduce
from heapq import heappop, heappush
from itertools import count
class Spell(namedtuple('BaseSpell',
'name cost effect turns damage heal armour mana')):
def __new__(cls, name, cost, effect=False, turns=None, damage=0, heal=0,
armour=0, mana=0):
return super().__new__(
cls, name, cost, effect, turns, damage, heal, armour, mana)
spells = (
Spell('Magic Missile', 53, damage=4),
Spell('Drain', 73, damage=2, heal=2),
Spell('Shield', 113, effect=True, turns=6, armour=7),
Spell('Poison', 173, effect=True, turns=6, damage=3),
Spell('Recharge', 229, effect=True, turns=5, mana=101),
)
class State(object):
def __init__(self, hp, mana, boss_hp, boss_damage,
mana_spent=0, effects=None, hard=False,
parent=None, spell_cast=None):
self.hp = hp
self.mana = mana
self.boss_hp = boss_hp
self.boss_damage = boss_damage
self.mana_spent = mana_spent
self.effects = effects or ()
self.hard = hard
self._parent = parent
self._spell_cast = spell_cast
def __eq__(self, other):
if not isinstance(other, State):
return NotImplemented
return all(getattr(self, k) == getattr(other, k)
for k in vars(self) if k[0] != '_')
def __hash__(self):
return reduce(lambda a, b: a ^ hash(b),
(v for k, v in vars(self).items() if k[0] != '_'), 0)
def iter_path(self):
if self._parent is None:
return
yield from self._parent.iter_path()
yield self._spell_cast
def process_effects(self, effects, hp, mana, boss_hp):
remaining_effects = []
armour = 0 # either Shield is in effect or it is not
for timer, effect in self.effects:
hp += effect.heal
mana += effect.mana
boss_hp -= effect.damage
armour = max(armour, effect.armour)
if timer > 1:
remaining_effects.append((timer - 1, effect))
return tuple(remaining_effects), hp, mana, boss_hp, armour
def boss_turn(self):
self.effects, self.hp, self.mana, self.boss_hp, armour = (
self.process_effects(
self.effects, self.hp, self.mana, self.boss_hp))
# only if the boss is still alive can they attack!
if self.boss_hp > 0:
self.hp -= max(1, self.boss_damage - armour)
def transitions(self):
# Player turn first
effects, hp, mana, boss_hp, __ = self.process_effects(
self.effects, self.hp - int(self.hard), self.mana, self.boss_hp)
for spell in spells:
if spell.cost > mana or any(spell is s for t, s in effects):
# can't cast spells for which we have no mana or in effect
continue
new_state = State(
hp, mana - spell.cost, boss_hp, self.boss_damage,
self.mana_spent + spell.cost, effects, hard=self.hard,
parent=self, spell_cast=spell.name)
if not spell.effect:
new_state.hp += spell.heal
new_state.boss_hp -= spell.damage
else:
new_state.effects = new_state.effects + ((spell.turns, spell),)
# Boss turn next
new_state.boss_turn()
# No point in playing a turn that has the player losing
if new_state.hp > 0:
yield new_state
def search_a_star(start):
open_states = {start}
pqueue = [(0, start)]
closed_states = set()
unique = count()
while open_states:
current = heappop(pqueue)[-1]
if current.boss_hp < 1:
return current
open_states.remove(current)
closed_states.add(current)
for state in current.transitions():
if state in closed_states or state in open_states:
continue
open_states.add(state)
heappush(pqueue, (state.mana_spent, next(unique), state))
if __name__ == '__main__':
import sys
filename = sys.argv[-1]
with open(filename) as f:
boss_hp = int(next(f).rpartition(':')[-1])
boss_attack = int(next(f).rpartition(':')[-1])
player_hp, player_mana = 50, 500
start = State(player_hp, player_mana, boss_hp, boss_attack)
end = search_a_star(start)
print('Part 1:', end.mana_spent)
if '-v' in sys.argv:
print(*end.iter_path(), sep=' -> ')
start.hard = True
end = search_a_star(start)
print('Part 2:', end.mana_spent)
if '-v' in sys.argv:
print(*end.iter_path(), sep=' -> ')
import re
class CPU(object):
def __init__(self, program, register_a=0):
self.registers = {'a': register_a, 'b': 0}
self.pos = 0
self.program = program
def run(self):
while 0 <= self.pos < len(self.program):
self.pos = self.execute()
return self.registers['b']
def execute(self ):
op, *args = self.program[self.pos]
return getattr(self, 'op_' + op)(*args)
def op_hlf(self, r):
self.registers[r] >>= 1
return self.pos + 1
def op_tpl(self, r):
self.registers[r] *= 3
return self.pos + 1
def op_inc(self, r):
self.registers[r] += 1
return self.pos + 1
def op_jmp(self, offset):
return self.pos + int(offset)
def op_jie(self, r, offset):
return self.pos + (int(offset) if not self.registers[r] % 2 else 1)
def op_jio(self, r, offset):
return self.pos + (int(offset) if self.registers[r] == 1 else 1)
if __name__ == '__main__':
import sys
filename = sys.argv[-1]
with open(filename) as f:
instructions = [re.split(r'[ ,]+', line.strip()) for line in f]
cpu = CPU(instructions)
register_b = cpu.run()
print('Part 1:', register_b)
cpu = CPU(instructions, 1)
register_b = cpu.run()
print('Part 2:', register_b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment