From 5c38a190ac6ddd4033636a4ca7d9a7b16903a80c Mon Sep 17 00:00:00 2001 From: derailed-dash Date: Tue, 19 Sep 2023 00:11:45 +0100 Subject: [PATCH] Script to upload to pypi --- .env | Bin 185 -> 406 bytes scripts/build_aoc_commons.ps1 | 21 ++++ scripts/upload_to_pypi.py | 25 +++++ .../reindeer_chemistry.py | 6 +- .../elf_delivery.py | 10 +- .../boss_fight.py | 6 +- .../spell_casting.py | 8 +- .../spell_casting_old.py | 6 +- src/AoC_2015/d23_computer/computer.py | 6 +- .../sleigh_balance.py | 8 +- .../instr_manual_codes.py | 6 +- .../instr_manual_codes_numpy.py | 6 +- .../d01_calorie_counting/elf_calories.py | 4 +- .../hill_climbing_E_to_a.py | 4 +- .../README.md | 2 +- .../aoc_common/__init__.py | 0 .../aoc_common/aoc_commons.py | 81 +++++++------ src/aoc_common/setup.py | 28 +++++ .../aoc_common/tests/input/input.txt | Bin 7022 -> 0 bytes src/aoc_commons_package/setup.py | 28 ----- src/template_folder/template.py | 8 +- .../aoc_common => }/tests/test_aoc_commons.py | 106 +++++++++--------- 22 files changed, 212 insertions(+), 157 deletions(-) create mode 100644 scripts/build_aoc_commons.ps1 create mode 100644 scripts/upload_to_pypi.py rename src/{aoc_commons_package => aoc_common}/README.md (80%) rename src/{aoc_commons_package => }/aoc_common/__init__.py (100%) rename src/{aoc_commons_package => }/aoc_common/aoc_commons.py (89%) create mode 100644 src/aoc_common/setup.py delete mode 100644 src/aoc_commons_package/aoc_common/tests/input/input.txt delete mode 100644 src/aoc_commons_package/setup.py rename src/{aoc_commons_package/aoc_common => }/tests/test_aoc_commons.py (64%) diff --git a/.env b/.env index 36ca02350f401af89745e8742027fdf9c366eabb..96444a78d1d131e29a2f117e3a5658bc75901725 100644 GIT binary patch literal 406 zcmV;H0crjKM@dveQdv+`04|Jf*l#|4r?#nYp7YWGuD@rh>Y0WO*^Q>O2JAqz__t(Z z6krkwvsfv>+adwekyp;AZp2I3dm(ra(Qh8UjXfYB5Z&KffKO7hk<0 zZ}GwyR@=LRwWWBFLc2}`+uRoV0MbC>r~?9Vz#|e5iAyTLxh6XVeD literal 185 zcmV;q07m}+M@dveQdv+`04b8!$dhzdPbQe!V{HA|Nb5OhDf^hqU5{9fx6T}h(Xudv zE4kCSZ8GLG)pX4JgmqGZiJX5<%7>;{Po6P1zGKGN+KX$xfG-gTF+j-UW#j53p-Zmt zXpb04J(idQ`LBJ=RrIE;Q+S^Fjyt<;O3Hnh270hkwJsnLLrkq|e$UwTFHrvA#RZO^ nEgyozpa9Gl4U2Bq6h!p!#v`C@$j$5*?jXrl`0?W9?4p#KYe-+d diff --git a/scripts/build_aoc_commons.ps1 b/scripts/build_aoc_commons.ps1 new file mode 100644 index 00000000..a5966080 --- /dev/null +++ b/scripts/build_aoc_commons.ps1 @@ -0,0 +1,21 @@ +# Author: Darren +# +# Utility script to build dazbo-aoc-commons and upload it to PyPI. +# Then, install the module: +# py -m pip install dazbo-aoc-commons + +Set-Location C:\Users\djl\localdev\Python\Advent-of-Code\scripts +Set-Location ..\src\aoc_common\ +"`nDeleting dist folder..." +if (Test-Path "dist") { + Remove-Item -LiteralPath "dist" -Recurse -Force +} + +"`nRunning package build..." +py -m setup sdist + +"`nUploading to PyPi..." +py ..\..\scripts\upload_to_pypi.py + +"`nResetting folder." +Set-Location ..\.. diff --git a/scripts/upload_to_pypi.py b/scripts/upload_to_pypi.py new file mode 100644 index 00000000..e974ac6c --- /dev/null +++ b/scripts/upload_to_pypi.py @@ -0,0 +1,25 @@ +""" Python to upload the dist folder to PyPi """ +import os +import subprocess +from dotenv import load_dotenv + +def upload_to_pypi(): + """ + So that we can skip having to run this command (and pass in username and pwd) separately: + twine upload dist/* + """ + load_dotenv() + + # expect vars to be stored in .env or environment vars + username = os.getenv('PYPI_USERNAME') + api_key = os.getenv('PYPI_API_KEY') + + if username is None or api_key is None: + raise ValueError("PYPI_USERNAME and/or PYPI_PASSWORD are not set in the .env file") + + try: + subprocess.run(['twine', 'upload', 'dist/*', '-u', username, '-p', api_key], check=True) + except subprocess.CalledProcessError as e: + print(f"An error occurred during the upload: {e}") + +upload_to_pypi() diff --git a/src/AoC_2015/d19_molecular_retrosynthesis_str_replacement/reindeer_chemistry.py b/src/AoC_2015/d19_molecular_retrosynthesis_str_replacement/reindeer_chemistry.py index 06a98814..26a8937b 100644 --- a/src/AoC_2015/d19_molecular_retrosynthesis_str_replacement/reindeer_chemistry.py +++ b/src/AoC_2015/d19_molecular_retrosynthesis_str_replacement/reindeer_chemistry.py @@ -44,10 +44,10 @@ import re import logging from collections import defaultdict -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.INFO) def main(): diff --git a/src/AoC_2015/d20_elf_visits_counting_generators_factors_and_set_algebra/elf_delivery.py b/src/AoC_2015/d20_elf_visits_counting_generators_factors_and_set_algebra/elf_delivery.py index 55066bc6..5a8cbf73 100644 --- a/src/AoC_2015/d20_elf_visits_counting_generators_factors_and_set_algebra/elf_delivery.py +++ b/src/AoC_2015/d20_elf_visits_counting_generators_factors_and_set_algebra/elf_delivery.py @@ -35,10 +35,10 @@ import time from collections import defaultdict import logging -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.INFO) TARGET = 36000000 @@ -53,7 +53,7 @@ def main(): presents_dropped, house_num = 0, 0 while presents_dropped < TARGET: house_num += 1 - presents_dropped = sum(factor * 10 for factor in td.get_factors(house_num)) + presents_dropped = sum(factor * 10 for factor in ac.get_factors(house_num)) logger.info("Part 1: House=%d, presents dropped=%d", house_num, presents_dropped) @@ -77,7 +77,7 @@ def generate_presents_for_house(per_elf_multiplier: int, elf_visit_limit: int = while True: # iterate for each house, yielding each time house_num += 1 presents_dropped = 0 - factors_for_house = td.get_factors(house_num) + factors_for_house = ac.get_factors(house_num) # iterate through all the factors for this house for factor in factors_for_house: diff --git a/src/AoC_2015/d21_boss_fight_classes_namedtuples_combinations_comprehension_if/boss_fight.py b/src/AoC_2015/d21_boss_fight_classes_namedtuples_combinations_comprehension_if/boss_fight.py index 07a91440..4730f0be 100644 --- a/src/AoC_2015/d21_boss_fight_classes_namedtuples_combinations_comprehension_if/boss_fight.py +++ b/src/AoC_2015/d21_boss_fight_classes_namedtuples_combinations_comprehension_if/boss_fight.py @@ -32,10 +32,10 @@ from os import path from itertools import combinations from player import Player -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.DEBUG) BOSS_FILE = "boss_stats.txt" diff --git a/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting.py b/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting.py index aa545ae6..3d85660f 100644 --- a/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting.py +++ b/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting.py @@ -40,10 +40,10 @@ from os import path from typing import Iterable -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.INFO) # td.setup_file_logging(logger, folder=locations.output_dir) @@ -372,7 +372,7 @@ def attack_combos_generator(count_different_attacks: int) -> Iterable[str]: i = 0 while True: # convert i to base-n (where n is the number of attacks we can choose from) - yield td.to_base_n(i, count_different_attacks) + yield ac.to_base_n(i, count_different_attacks) i += 1 @cache # I think there are only about 3000 different sorted attacks diff --git a/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting_old.py b/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting_old.py index 8ceaf4cc..293af062 100644 --- a/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting_old.py +++ b/src/AoC_2015/d22_wizards_factories_dataclass_generators/spell_casting_old.py @@ -30,10 +30,10 @@ import time from os import path from typing import Iterable -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.INFO) # td.setup_file_logging(logger, folder=locations.script_dir) diff --git a/src/AoC_2015/d23_computer/computer.py b/src/AoC_2015/d23_computer/computer.py index f90e2e64..cda26702 100644 --- a/src/AoC_2015/d23_computer/computer.py +++ b/src/AoC_2015/d23_computer/computer.py @@ -17,10 +17,10 @@ """ import logging import time -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.INFO) class Instructions(): diff --git a/src/AoC_2015/d24_sleigh_balance_subset_sum/sleigh_balance.py b/src/AoC_2015/d24_sleigh_balance_subset_sum/sleigh_balance.py index b3727f85..aaa80f51 100644 --- a/src/AoC_2015/d24_sleigh_balance_subset_sum/sleigh_balance.py +++ b/src/AoC_2015/d24_sleigh_balance_subset_sum/sleigh_balance.py @@ -30,15 +30,15 @@ import time from math import prod from itertools import combinations -import aoc_common.aoc_commons as td +import aoc_commons as ac YEAR = 2015 DAY = 24 -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.INFO) -td.write_puzzle_input_file(YEAR, DAY, locations) +ac.write_puzzle_input_file(YEAR, DAY, locations) def main(): # with open(locations.sample_input_file, mode="rt") as f: diff --git a/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes.py b/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes.py index 62993fae..8afffdf7 100644 --- a/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes.py +++ b/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes.py @@ -21,10 +21,10 @@ """ import logging import time -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.DEBUG) TARGET_ROW = 2947 diff --git a/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes_numpy.py b/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes_numpy.py index c0ff3432..2866fa25 100644 --- a/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes_numpy.py +++ b/src/AoC_2015/d25_instr_manual_2d_list_gen/instr_manual_codes_numpy.py @@ -22,10 +22,10 @@ import logging import time import numpy as np -import aoc_common.aoc_commons as td +import aoc_commons as ac -locations = td.get_locations(__file__) -logger = td.retrieve_console_logger(locations.script_name) +locations = ac.get_locations(__file__) +logger = ac.retrieve_console_logger(locations.script_name) logger.setLevel(logging.DEBUG) TARGET_ROW = 2947 diff --git a/src/AoC_2022/d01_calorie_counting/elf_calories.py b/src/AoC_2022/d01_calorie_counting/elf_calories.py index b1fc2ae5..de3d7240 100644 --- a/src/AoC_2022/d01_calorie_counting/elf_calories.py +++ b/src/AoC_2022/d01_calorie_counting/elf_calories.py @@ -29,7 +29,7 @@ import logging from pathlib import Path import time -import aoc_common.aoc_commons as td +import aoc_commons as ac SCRIPT_NAME = Path(__file__).stem SCRIPT_DIR = Path(__file__).parent @@ -38,7 +38,7 @@ logger = logging.getLogger(SCRIPT_NAME) logger.setLevel(logging.DEBUG) -logger.addHandler(td.stream_handler) +logger.addHandler(ac.stream_handler) def main(): with open(INPUT_FILE, mode="rt") as f: diff --git a/src/AoC_2022/d12_hill_climbing_algorithm/hill_climbing_E_to_a.py b/src/AoC_2022/d12_hill_climbing_algorithm/hill_climbing_E_to_a.py index 73b5be36..82e07926 100644 --- a/src/AoC_2022/d12_hill_climbing_algorithm/hill_climbing_E_to_a.py +++ b/src/AoC_2022/d12_hill_climbing_algorithm/hill_climbing_E_to_a.py @@ -31,7 +31,7 @@ from pathlib import Path import time from matplotlib import pyplot as plt -import aoc_common.aoc_commons as td +import aoc_commons as ac from aoc_common.aoc_commons import Point SCRIPT_NAME = Path(__file__).stem @@ -42,7 +42,7 @@ logger = logging.getLogger(SCRIPT_NAME) logger.setLevel(logging.DEBUG) -logger.addHandler(td.stream_handler) +logger.addHandler(ac.stream_handler) # td.setup_file_logging(logger, OUTPUT_DIR, SCRIPT_NAME) class Grid(): diff --git a/src/aoc_commons_package/README.md b/src/aoc_common/README.md similarity index 80% rename from src/aoc_commons_package/README.md rename to src/aoc_common/README.md index 49f12fa7..8b3fa08a 100644 --- a/src/aoc_commons_package/README.md +++ b/src/aoc_common/README.md @@ -8,4 +8,4 @@ pip install dazbo-aoc-commons ## Use -import aoc_common.aoc_commons as td \ No newline at end of file +import aoc_commons as ac \ No newline at end of file diff --git a/src/aoc_commons_package/aoc_common/__init__.py b/src/aoc_common/__init__.py similarity index 100% rename from src/aoc_commons_package/aoc_common/__init__.py rename to src/aoc_common/__init__.py diff --git a/src/aoc_commons_package/aoc_common/aoc_commons.py b/src/aoc_common/aoc_commons.py similarity index 89% rename from src/aoc_commons_package/aoc_common/aoc_commons.py rename to src/aoc_common/aoc_commons.py index cf8a69cc..cbad4a84 100644 --- a/src/aoc_commons_package/aoc_common/aoc_commons.py +++ b/src/aoc_common/aoc_commons.py @@ -5,8 +5,8 @@ A set of helper functions, reusable classes and attributes used by my AoC solutions Test with tests/test_aoc_commons.py -You could import as follows: -import common.aoc_commons as td +You can import as follows: +import aoc_commons as ac """ # py -m pip install requests python-dotenv from __future__ import annotations @@ -111,7 +111,8 @@ class Locations: sample_input_file: Path input_file: Path -def get_locations(script_file): +def get_locations(script_file) -> Locations: + """ Set various paths, based on the location of the calling script. """ script_name = Path(script_file).stem # this script file, without .py script_dir = Path(script_file).parent # the folder where this script lives input_dir = Path(script_dir, "input") @@ -128,51 +129,61 @@ def get_locations(script_file): # Retrieving input data ################################################################## -def write_puzzle_input_file(year: int, day: int, locations: Locations) -> bool: - """ Use session key to obtain user's unique data for this year and day. - Only retrieve if the input file does not already exist. - Return True if retrieved; False if file exists. - """ - if os.path.exists(locations.input_file): - logger.debug("%s already exists", os.path.basename(locations.input_file)) - return False - - potential_paths = [ +def get_envs_from_file() -> bool: + """ Look for .env files, read variables from it, and store as environment variables """ + potential_paths = [ # look for .env '.env', os.path.join('..', '.env'), os.path.join('..', '..', '.env'), ] - env_path = "" for a_path in potential_paths: if os.path.exists(a_path): logger.info("Using .env at %s", a_path) - env_path = a_path + load_dotenv(a_path, verbose=True) + return True + + logger.warning("No .env file found.") + return False + +get_envs_from_file() # read env variables from a .env file, if we can find one + +def write_puzzle_input_file(year: int, day: int, locations: Locations) -> bool: + """ Use session key to obtain user's unique data for this year and day. + Only retrieve if the input file does not already exist. + Return True if successful. + Requires env: AOC_SESSION_COOKIE, which can be set from the .env. + """ + if os.path.exists(locations.input_file): + logger.debug("%s already exists", os.path.basename(locations.input_file)) + return True - load_dotenv(env_path) - SESSION_COOKIE = os.getenv('AOC_SESSION_COOKIE') - if SESSION_COOKIE: + session_cookie = os.getenv('AOC_SESSION_COOKIE') + if session_cookie: logger.info('Session cookie retrieved.') - else: - logger.error('Failed to retrieve session cookie. Is it in your .env?') - url = f"https://adventofcode.com/{year}/day/{day}/input" - cookies = {"session": SESSION_COOKIE} - response = requests.get(url, cookies=cookies) - data = "" - - if response.status_code == 200: - data = response.text + # Create input folder, if it doesn't exist + if not locations.input_dir.exists(): + locations.input_dir.mkdir(parents=True, exist_ok=True) + + url = f"https://adventofcode.com/{year}/day/{day}/input" + cookies = {"session": session_cookie} + response = requests.get(url, cookies=cookies, timeout=5) + + data = "" + if response.status_code == 200: + data = response.text + else: + data = f"Failed to retrieve puzzle input: {response.status_code}" + + with open(locations.input_file, 'w') as file: + logger.debug("Writing input file %s", os.path.basename(locations.input_file)) + file.write(data) + return True else: - data = f"Failed to retrieve puzzle input: {response.status_code}" - - if not locations.input_dir.exists(): - locations.input_dir.mkdir(parents=True, exist_ok=True) - logger.debug("Writing %s", os.path.basename(locations.input_file)) - with open(locations.input_file, 'w') as file: - file.write(data) + logger.error('Failed to retrieve session cookie. Is it in your .env?') - return True + return False ################################################################# # POINTS, VECTORS AND GRIDS diff --git a/src/aoc_common/setup.py b/src/aoc_common/setup.py new file mode 100644 index 00000000..0f62d66c --- /dev/null +++ b/src/aoc_common/setup.py @@ -0,0 +1,28 @@ +""" +Used to setup the aoc_commons package. +Make sure you have installed twine first. +py -m pip install twine + +1. Delete any existing dist folder from aoc_commons_package. +2. Before updating, be sure to increment the version number. +3. Create the package. From the aoc_commons_package folder, run: + py -m setup sdist +4. Upload to PyPi: + twine upload dist/* +5. Install the package from your venv at the project folder level: + py -m pip install dazbo-aoc-commons +""" +from setuptools import setup + +setup( + name='dazbo-aoc-commons', + version='0.1.10', + url='https://github.com/derailed-dash/Advent-of-Code', + author='derailed-dash', + description='A set up of helper functions and classes to assist with Advent of Code problems', + long_description=open('README.md').read(), + long_description_content_type="text/markdown", + py_modules=["aoc_commons"], + package_dir={'': '.'}, # Current directory + install_requires=[], +) diff --git a/src/aoc_commons_package/aoc_common/tests/input/input.txt b/src/aoc_commons_package/aoc_common/tests/input/input.txt deleted file mode 100644 index 8ca0ad5aa7bf187b0367a96f34f6b92f2c83269a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7022 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 None: + self.clear_input_folder() + return super().tearDown() + + def clear_input_folder(self): + """ Clear the input folder """ if self.locations.input_dir.exists(): print(f"Deleting {self.locations.input_dir}") rmtree(self.locations.input_dir) @@ -57,44 +55,44 @@ def test_write_puzzle_input_file(self): """ We can create an input folder and input file """ # Try to retrieve input that does not exist - self.assertTrue(write_puzzle_input_file(2010, 1, self.locations)) + self.assertTrue(ac.write_puzzle_input_file(2010, 1, self.locations)) with open(self.locations.input_file, "r") as file: data = file.read() self.assertIn("Failed", data) # Clear the folder and then retrieve legitimate input - self.clear_input_file() - self.assertTrue(write_puzzle_input_file(2015, 1, self.locations)) + self.clear_input_folder() + self.assertTrue(ac.write_puzzle_input_file(2015, 1, self.locations)) with open(self.locations.input_file, "r") as file: data = file.read() self.assertIn("(((())))", data) # Does not retrieve file if it already exists - self.assertFalse(write_puzzle_input_file(2015, 1, self.locations)) + self.assertTrue(ac.write_puzzle_input_file(2015, 1, self.locations)) def test_vectors(self): - self.assertEqual(Vectors.N.value, (0, 1)) - self.assertEqual(Vectors.NW.value, (-1, 1)) - self.assertEqual(Vectors.S.value, (0, -1)) - self.assertEqual(Vectors.E.value, (1, 0)) - self.assertEqual(Vectors.N.y_inverted, (0, -1)) - self.assertEqual(Vectors.SW.value, (-1, -1)) + self.assertEqual(ac.Vectors.N.value, (0, 1)) + self.assertEqual(ac.Vectors.NW.value, (-1, 1)) + self.assertEqual(ac.Vectors.S.value, (0, -1)) + self.assertEqual(ac.Vectors.E.value, (1, 0)) + self.assertEqual(ac.Vectors.N.y_inverted, (0, -1)) + self.assertEqual(ac.Vectors.SW.value, (-1, -1)) def test_vector_dicts(self): - self.assertEqual(VectorDicts.ARROWS[">"], (1, 0)) - self.assertEqual(VectorDicts.ARROWS["v"], (0, -1)) - self.assertEqual(VectorDicts.DIRS["L"], (-1, 0)) - self.assertEqual(VectorDicts.DIRS["U"], (0, 1)) - self.assertEqual(VectorDicts.NINE_BOX["tr"], (1, 1)) - self.assertEqual(VectorDicts.NINE_BOX["bl"], (-1, -1)) + self.assertEqual(ac.VectorDicts.ARROWS[">"], (1, 0)) + self.assertEqual(ac.VectorDicts.ARROWS["v"], (0, -1)) + self.assertEqual(ac.VectorDicts.DIRS["L"], (-1, 0)) + self.assertEqual(ac.VectorDicts.DIRS["U"], (0, 1)) + self.assertEqual(ac.VectorDicts.NINE_BOX["tr"], (1, 1)) + self.assertEqual(ac.VectorDicts.NINE_BOX["bl"], (-1, -1)) def test_point_arithmetic(self): self.assertEqual(self.a_point + self.b_point, self.c_point, "Asserting Point addition") self.assertEqual(self.a_point - self.b_point, self.d_point, "Asserting Point subtraction") - self.assertEqual(self.b_point * Point(3, 3), self.e_point, "Asserting multiplication") + self.assertEqual(self.b_point * ac.Point(3, 3), self.e_point, "Asserting multiplication") def test_manhattan_distance(self): - self.assertEqual(Point.manhattan_distance(self.e_point), 3+6) + self.assertEqual(ac.Point.manhattan_distance(self.e_point), 3+6) self.assertEqual(self.c_point.manhattan_distance_from(self.b_point), abs(self.a_point.x)+abs(self.a_point.y)) def test_point_containers(self): @@ -133,18 +131,18 @@ def test_grid(self): "2176841721"] input_array_data = [[int(posn) for posn in row] for row in input_grid] - grid = Grid(input_array_data) + grid = ac.Grid(input_array_data) self.assertEqual(grid.height, len(input_grid)) self.assertEqual(grid.width, len(input_grid[0])) - self.assertTrue(grid.valid_location(Point(1, 1))) - self.assertFalse(grid.valid_location(Point(11,8))) - self.assertEqual(grid.value_at_point(Point(1, 1)), 7) + self.assertTrue(grid.valid_location(ac.Point(1, 1))) + self.assertFalse(grid.valid_location(ac.Point(11,8))) + self.assertEqual(grid.value_at_point(ac.Point(1, 1)), 7) self.assertEqual(grid.rows_as_str()[0], "5483143223") self.assertEqual(grid.cols_as_str()[0], "5256642") def test_binary_search(self): - self.assertEqual(binary_search(225, 0, 20, lambda x: x**2, reverse_search=True), None) - self.assertEqual(binary_search(225, 0, 20, lambda x: x**2), 15) + self.assertEqual(ac.binary_search(225, 0, 20, lambda x: x**2, reverse_search=True), None) + self.assertEqual(ac.binary_search(225, 0, 20, lambda x: x**2), 15) def test_merge_intervals(self): pairs = [ @@ -157,18 +155,18 @@ def test_merge_intervals(self): expected = [[1, 7], [8, 15], [18, 20]] - self.assertEqual(merge_intervals(pairs), expected) + self.assertEqual(ac.merge_intervals(pairs), expected) def test_get_factors(self): expected = {1, 2, 4, 8} - self.assertEqual(get_factors(8), expected) + self.assertEqual(ac.get_factors(8), expected) def test_to_base_n(self): - self.assertEqual(to_base_n(10, 2), "1010") - self.assertEqual(to_base_n(38, 5), "123") - self.assertEqual(to_base_n(24, 12), "20") - self.assertEqual(to_base_n(0, 12), "0") - self.assertEqual(to_base_n(57, 10), "57") + self.assertEqual(ac.to_base_n(10, 2), "1010") + self.assertEqual(ac.to_base_n(38, 5), "123") + self.assertEqual(ac.to_base_n(24, 12), "20") + self.assertEqual(ac.to_base_n(0, 12), "0") + self.assertEqual(ac.to_base_n(57, 10), "57") if __name__ == "__main__": unittest.main(verbosity=2)