My Advent of Code solutions in Python. kevinyap.ca/2019/12/going-fast-in-advent-of-code/
advent-of-code python
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 170 lines 4.8 kB view raw
1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4import os 5import re 6import sys 7import glob 8import argparse 9import resource 10import subprocess 11 12try: 13 from halo import Halo 14except ImportError: 15 # Use a noop context manager if Halo isn't installed 16 from contextlib import contextmanager 17 18 @contextmanager 19 def Halo(text): 20 yield 21 22 23class bcolors: 24 HEADER = '\033[95m' 25 OKBLUE = '\033[94m' 26 OKGREEN = '\033[92m' 27 WARNING = '\033[93m' 28 FAIL = '\033[91m' 29 ENDC = '\033[0m' 30 31 32def clock(): 33 return resource.getrusage(resource.RUSAGE_CHILDREN)[0] 34 35 36def color_time_str(str, timespan): 37 if abs(timespan) >= 10: 38 color = bcolors.FAIL 39 elif abs(timespan) >= 1: 40 color = bcolors.WARNING 41 else: 42 color = '' 43 44 return '{}{}{}'.format(color, str, bcolors.ENDC) 45 46 47def format_time(timespan, padding=None): 48 """Formats the timespan in a human readable format""" 49 if timespan >= 1.0: 50 time_str = '{:.3g} s'.format(timespan) 51 else: 52 time_str = '{:.3g} ms'.format(timespan * 1e3) 53 54 if padding is not None: 55 time_str = time_str.rjust(padding) 56 57 return color_time_str(time_str, timespan) 58 59 60def check_solution(program, day, input_file, output_file, pypy=False): 61 with Halo(text='Day {:02}'.format(day)): 62 cmd = ['pypy' if pypy else 'python', program, input_file] 63 start = clock() 64 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 65 stdout = proc.communicate()[0] 66 end = clock() 67 cpu_usr = end - start 68 69 valid = True 70 71 with open(output_file) as f: 72 for line in f: 73 if line.strip() not in stdout: 74 valid = False 75 break 76 77 return valid, stdout, cpu_usr 78 79 80if __name__ == '__main__': 81 parser = argparse.ArgumentParser(description="Advent of Code puzzle runner.") 82 parser.add_argument('year', type=int) 83 parser.add_argument('puzzles', type=int, metavar='puzzle', nargs='*') 84 parser.add_argument('--pypy', const=True, action='store_const', 85 help="use PyPy instead of CPython") 86 parser.add_argument('--benchmark', const=True, action='store_const', 87 help="compare PyPy against CPython") 88 89 args = parser.parse_args() 90 91 year = args.year 92 puzzles = args.puzzles 93 pypy = args.pypy 94 95 if puzzles: 96 programs = [] 97 for p in puzzles: 98 programs.extend(glob.glob('%s/day%02i.py' % (year, p))) 99 else: 100 programs = glob.glob('%s/day*.py' % year) 101 102 to_run = [] 103 104 for program in sorted(programs): 105 try: 106 day = int(re.findall(r'(\d+).py', program)[0]) 107 except IndexError: 108 continue 109 input_file = '%s/inputs/%02i.txt' % (year, day) 110 output_file = '%s/outputs/%02i.txt' % (year, day) 111 112 if os.path.exists(output_file): 113 to_run.append((program, day, input_file, output_file)) 114 115 if args.benchmark: 116 print "Day CPython PyPy Delta Speedup" 117 print '-' * 45 118 119 for program, day, input_file, output_file in to_run: 120 cpy_time = check_solution(program, day, input_file, output_file, pypy=False)[2] 121 pypy_time = check_solution(program, day, input_file, output_file, pypy=True)[2] 122 123 print "{:02} {} {} {} {:0.1f}".format( 124 day, 125 format_time(cpy_time, padding=7), 126 format_time(pypy_time, padding=7), 127 format_time(cpy_time - pypy_time, padding=9), 128 cpy_time / pypy_time, 129 ) 130 131 sys.exit(0) 132 133 exit_code = 0 134 runtimes = [] 135 136 for program, day, input_file, output_file in to_run: 137 valid, stdout, cpu_usr = check_solution(program, day, input_file, output_file, pypy) 138 runtimes.append(cpu_usr) 139 140 print '{}{}{} Day {:02} ({})'.format( 141 bcolors.OKGREEN if valid else bcolors.FAIL, 142 '' if valid else '', 143 bcolors.ENDC, 144 day, 145 format_time(cpu_usr), 146 ) 147 print stdout 148 149 if not valid: 150 exit_code = 1 151 152 if len(puzzles) != 1: 153 print "Total runtime:", format_time(sum(runtimes)) 154 155 cutoffs = [ 156 0.025, 0.050, 0.075, 0.100, 0.125, 0.150, 157 0.200, 0.250, 0.300, 0.400, 0.500, 0.750, 158 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 159 ] 160 161 cutoffs.extend(range(5, 10)) 162 cutoffs.extend(range(10, 20, 2)) 163 cutoffs.extend(range(20, 120, 3)) 164 165 for day, runtime in enumerate(runtimes, start=1): 166 out = "Day {:02}: {}".format(day, format_time(runtime, padding=7)) 167 bar_len = next(i + 1 for i, cutoff in enumerate(cutoffs) if runtime < cutoff) 168 print out, color_time_str('' * bar_len, runtime) 169 170 sys.exit(exit_code)