From 2b8a69c24b9ce854737d2b65c2a7932d5e1a2163 Mon Sep 17 00:00:00 2001 From: Stefan Harmuth Date: Sun, 21 Nov 2021 13:04:32 +0100 Subject: [PATCH] prep for 2021; implemented 2017d1 as reference --- aoclib/__init__.py | 4 ++ aoclib/day.py | 112 ++++++++++++++++++++++++++++++++++++++++ aoclib/output.py | 8 +++ aoclib/performance.py | 8 +++ aoclib/tools.py | 8 +++ day01.py | 28 ++++++++++ inputs/input01 | 1 + inputs/test_input01_1_0 | 1 + inputs/test_input01_1_1 | 1 + inputs/test_input01_1_2 | 1 + inputs/test_input01_1_3 | 1 + inputs/test_input01_2_0 | 1 + inputs/test_input01_2_1 | 1 + inputs/test_input01_2_2 | 1 + inputs/test_input01_2_3 | 1 + inputs/test_input01_2_4 | 1 + main.py | 83 +++++++++++++++++++++++++---- 17 files changed, 251 insertions(+), 10 deletions(-) create mode 100644 aoclib/__init__.py create mode 100644 aoclib/day.py create mode 100644 aoclib/output.py create mode 100644 aoclib/performance.py create mode 100644 aoclib/tools.py create mode 100644 day01.py create mode 100644 inputs/input01 create mode 100644 inputs/test_input01_1_0 create mode 100644 inputs/test_input01_1_1 create mode 100644 inputs/test_input01_1_2 create mode 100644 inputs/test_input01_1_3 create mode 100644 inputs/test_input01_2_0 create mode 100644 inputs/test_input01_2_1 create mode 100644 inputs/test_input01_2_2 create mode 100644 inputs/test_input01_2_3 create mode 100644 inputs/test_input01_2_4 diff --git a/aoclib/__init__.py b/aoclib/__init__.py new file mode 100644 index 0000000..e06a1d0 --- /dev/null +++ b/aoclib/__init__.py @@ -0,0 +1,4 @@ +from .day import * +from .tools import * +from .output import * +from .performance import * diff --git a/aoclib/day.py b/aoclib/day.py new file mode 100644 index 0000000..00cabef --- /dev/null +++ b/aoclib/day.py @@ -0,0 +1,112 @@ +import os +from typing import List, Any +from .tools import splitLine +from .output import printSolution + +BASE_PATH = os.path.dirname(os.path.dirname(__file__)) +INPUTS_PATH = os.path.join(BASE_PATH, 'inputs') + + +class AOCDay: + day: int + input: List # our input is always a list of str/lines + test_solutions_p1: List + test_solutions_p2: List + + def __init__(self, day): + self.day = day + with open(os.path.join(INPUTS_PATH, "input%02d" % day)) as f: + self.input = f.read().splitlines() + + def part1(self) -> Any: + pass + + def part2(self) -> Any: + pass + + def test_part1(self) -> bool: + live_input = self.input.copy() + for case, solution in enumerate(self.test_solutions_p1): + with open(os.path.join(INPUTS_PATH, "test_input%02d_1_%d" % (self.day, case))) as f: + self.input = f.read().splitlines() + + check = self.part1() + printSolution(self.day, 1, check, solution, case) + if check != solution: + return False + + self.input = live_input + return True + + def test_part2(self) -> bool: + live_input = self.input.copy() + for case, solution in enumerate(self.test_solutions_p2): + with open(os.path.join(INPUTS_PATH, "test_input%02d_2_%d" % (self.day, case))) as f: + self.input = f.read().splitlines() + + check = self.part2() + printSolution(self.day, 2, check, solution, case) + if check != solution: + return False + + self.input = live_input + return True + + def getInputListAsType(self, return_type): + """ + get input as list casted to return_type, each line representing one list entry + """ + return [return_type(i) for i in self.input] + + def getMultiLineInputAsArray(self, return_type=None, join_char=None): + """ + get input for day x as 2d array, split by empty lines + """ + lines = self.input + lines.append('') + + return_array = [] + line_array = [] + for line in lines: + if not line: + if join_char: + return_array.append(join_char.join(line_array)) + else: + return_array.append(line_array) + line_array = [] + continue + + if return_type: + line_array.append(return_type(line)) + else: + line_array.append(line) + + return return_array + + def getInputAs2DArray(self, return_type=None): + """ + get input for day x as 2d-array (a[line][pos]) + """ + if return_type is None: + return self.input # strings already act like a list + else: + return_array = [] + for line in self.input: + return_array.append([return_type(i) for i in line]) + + return return_array + + def getInputAsArraySplit(self, split_char=',', return_type=None): + """ + get input for day x with the lines split by split_char + if input has only one line, returns a 1d array with the values + if input has multiple lines, returns a 2d array (a[line][values]) + """ + if len(self.input) == 1: + return splitLine(line=self.input[0], split_char=split_char, return_type=return_type) + else: + return_array = [] + for line in self.input: + return_array.append(splitLine(line=line, split_char=split_char, return_type=return_type)) + + return return_array diff --git a/aoclib/output.py b/aoclib/output.py new file mode 100644 index 0000000..06dc6b0 --- /dev/null +++ b/aoclib/output.py @@ -0,0 +1,8 @@ +def printSolution(day, part, solution, test=None, test_case=0): + if test is not None: + print( + "(TEST case%d/day%d/part%d) -- got '%s' -- expected '%s' -> %s" + % (test_case, day, part, solution, test, "correct" if test == solution else "WRONG") + ) + else: + print("Solution to day %s, part %s: %s" % (day, part, solution)) diff --git a/aoclib/performance.py b/aoclib/performance.py new file mode 100644 index 0000000..8b57019 --- /dev/null +++ b/aoclib/performance.py @@ -0,0 +1,8 @@ +def print_execution_time(day, part, avg_time): + units = ['s', 'ms', 'μs', 'ns'] + unit = 0 + while avg_time < 1: + avg_time *= 1000 + unit += 1 + + print("Average execution time for day %s part %s: %1.2f%s" % (day, part, avg_time, units[unit])) diff --git a/aoclib/tools.py b/aoclib/tools.py new file mode 100644 index 0000000..cc03d36 --- /dev/null +++ b/aoclib/tools.py @@ -0,0 +1,8 @@ +def splitLine(line, split_char=',', return_type=None): + if split_char: + line = line.split(split_char) + + if return_type is None: + return line + else: + return [return_type(i) for i in line] diff --git a/day01.py b/day01.py new file mode 100644 index 0000000..c5c189b --- /dev/null +++ b/day01.py @@ -0,0 +1,28 @@ +from aoclib import AOCDay + + +class Day(AOCDay): + test_solutions_p1 = [3, 4, 0, 9] + test_solutions_p2 = [6, 0, 4, 12, 4] + + def part1(self): + res = 0 + number = self.input[0] + if number[0] == number[-1]: + res += int(number[0]) + + for i in range(len(number) - 1): + if number[i] == number[i+1]: + res += int(number[i]) + + return res + + def part2(self): + res = 0 + number = self.input[0] + half = len(number) // 2 + for i in range(half): + if number[i] == number[i+half]: + res += int(number[i]) * 2 + + return res diff --git a/inputs/input01 b/inputs/input01 new file mode 100644 index 0000000..c2c2085 --- /dev/null +++ b/inputs/input01 @@ -0,0 +1 @@ +516299281491169512719425276194596424291268712697155863651846937925928456958813624428156218468331423858422613471962165756423837756856519754524985759763747559711257977361228357678293572698839754444752898835313399815748562519958329927911861654784216355489319995566297499836295985943899373615223375271231128914745273184498915241488393761676799914385265459983923743146555465177886491979962465918888396664233693243983969412682561799628789569294374554575677368219724142536789649121758582991345537639888858113763738518511184439854223386868764189133964543721941169786274781775658991329331759679943342217578532643519615296424396487669451453728113114748217177826874953466435436129165295379157226345786756899935747336785161745487933721527239394118721517195849186676814232887413175587327214144876898248571248517121796766248817366614333915154796983612174281237846165129114988453188844745119798643314857871527757831265298846833327863781341559381238458322786192379487455671563757123534253463563421716138641611915686247343417126655317378639314168461345613427262786624689498485599942336813995725145169355942616672812792174556866436158375938988738721253664772584577384558696477546232189312287262439452141564522329987139692281984783513691857538335537553448919819545332125483128878925492334361562192621672993868479566688564752226111784486619789588318171745995253645886833872665447241245329935643883892447524286642296955354249478815116517315832179925494818748478164317669471654464867111924676961162162841232473474394739793968624974397916495667233337397241933765513777241916359166994384923869741468174653353541147616645393917694581811193977311981752554551499629219873391493426883886536219455848354426461562995284162323961773644581815633779762634745339565196798724847722781666948626231631632144371873154872575615636322965353254642186897127423352618879431499138418872356116624818733232445649188793318829748789349813295218673497291134164395739665667255443366383299669973689528188264386373591424149784473698487315316676637165317972648916116755224598519934598889627918883283534261513179931798591959489372165295 diff --git a/inputs/test_input01_1_0 b/inputs/test_input01_1_0 new file mode 100644 index 0000000..aa309cc --- /dev/null +++ b/inputs/test_input01_1_0 @@ -0,0 +1 @@ +1122 diff --git a/inputs/test_input01_1_1 b/inputs/test_input01_1_1 new file mode 100644 index 0000000..5f2f16b --- /dev/null +++ b/inputs/test_input01_1_1 @@ -0,0 +1 @@ +1111 diff --git a/inputs/test_input01_1_2 b/inputs/test_input01_1_2 new file mode 100644 index 0000000..81c545e --- /dev/null +++ b/inputs/test_input01_1_2 @@ -0,0 +1 @@ +1234 diff --git a/inputs/test_input01_1_3 b/inputs/test_input01_1_3 new file mode 100644 index 0000000..1d95b9e --- /dev/null +++ b/inputs/test_input01_1_3 @@ -0,0 +1 @@ +91212129 diff --git a/inputs/test_input01_2_0 b/inputs/test_input01_2_0 new file mode 100644 index 0000000..1f8248c --- /dev/null +++ b/inputs/test_input01_2_0 @@ -0,0 +1 @@ +1212 diff --git a/inputs/test_input01_2_1 b/inputs/test_input01_2_1 new file mode 100644 index 0000000..3acca38 --- /dev/null +++ b/inputs/test_input01_2_1 @@ -0,0 +1 @@ +1221 diff --git a/inputs/test_input01_2_2 b/inputs/test_input01_2_2 new file mode 100644 index 0000000..ab5c88c --- /dev/null +++ b/inputs/test_input01_2_2 @@ -0,0 +1 @@ +123425 diff --git a/inputs/test_input01_2_3 b/inputs/test_input01_2_3 new file mode 100644 index 0000000..3d4db09 --- /dev/null +++ b/inputs/test_input01_2_3 @@ -0,0 +1 @@ +123123 diff --git a/inputs/test_input01_2_4 b/inputs/test_input01_2_4 new file mode 100644 index 0000000..8f237ab --- /dev/null +++ b/inputs/test_input01_2_4 @@ -0,0 +1 @@ +12131415 diff --git a/main.py b/main.py index 76d0e8c..e551860 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,79 @@ -# This is a sample Python script. +#!/usr/bin/env python3 -# Press Umschalt+F10 to execute it or replace it with your code. -# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. +import aoclib +import argparse +import importlib +import os +import sys +import timeit +TIMEIT_NUMBER = 50 -def print_hi(name): - # Use a breakpoint in the code line below to debug your script. - print(f'Hi, {name}') # Press Strg+F8 to toggle the breakpoint. +argument_parser = argparse.ArgumentParser() +argument_parser.add_argument("-d", "--day", help="specify day to process; leave empty for ALL days", type=int) +argument_parser.add_argument("-t", "--test", help="run test cases", action="store_true", default=False) +argument_parser.add_argument("-p", "--part", help="run only part x", choices=[1, 2], type=int) +argument_parser.add_argument("--timeit", help="measure execution time", action="store_true", default=False) +argument_parser.add_argument( + "--timeit-number", + help="build average time over this many executions", + type=int, + default=TIMEIT_NUMBER +) +flags = argument_parser.parse_args() +import_day = "" +if flags.day: + import_day = "%02d" % flags.day -# Press the green button in the gutter to run the script. -if __name__ == '__main__': - print_hi('PyCharm') +imported = [] +for _, _, files in os.walk(aoclib.BASE_PATH): + for f in files: + if f.startswith('day' + import_day) and f.endswith('.py'): + lib_name = f[:-3] + globals()[lib_name] = importlib.import_module(lib_name) + imported.append(lib_name) -# See PyCharm help at https://www.jetbrains.com/help/pycharm/ + break + +for lib in sorted(imported): + day = int(lib[-2:]) + day_class = getattr(globals()[lib], "Day")(day) + if not flags.test: + if not flags.part or flags.part == 1: + if not day_class.test_part1(): + print("TEST FAILED! Aborting.") + sys.exit(1) + + if not flags.part or flags.part == 2: + if not day_class.test_part2(): + print("TEST FAILED! Aborting.") + sys.exit(1) + + if not flags.part or flags.part == 1: + if not flags.timeit: + if flags.test: + day_class.test_part1() + else: + aoclib.printSolution(day, 1, day_class.part1()) + else: + exec_time = timeit.timeit( + 'day_class.part1()', + globals=globals(), + number=flags.timeit_number + ) / flags.timeit_number + aoclib.print_execution_time(day, 1, exec_time) + + if not flags.part or flags.part == 2: + if not flags.timeit: + if flags.test: + day_class.test_part2() + else: + aoclib.printSolution(day, 2, day_class.part2()) + else: + exec_time = timeit.timeit( + 'day_class.part2()', + globals=globals(), + number=flags.timeit_number + ) / flags.timeit_number + aoclib.print_execution_time(day, 2, exec_time)