├── .deepsource.toml ├── .editorconfig ├── .github ├── CODEOWNERS └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── advent_of_code_ocr ├── __init__.py └── characters.py ├── codecov.yml ├── pyproject.toml └── tests └── test_6.py /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | test_patterns = ["/tests/test_*.py"] 4 | 5 | [[analyzers]] 6 | name = "python" 7 | enabled = true 8 | 9 | [analyzers.meta] 10 | runtime_version = "3.x.x" 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | indent_size = 4 9 | indent_style = space 10 | 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @bsoyka 2 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload to PyPI 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: "3.x" 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install flit 21 | - name: Build and publish 22 | env: 23 | FLIT_USERNAME: __token__ 24 | FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 25 | run: | 26 | flit publish --setup-py 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test with pytest 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | python-version: ["3.7", "3.8", "3.9", "3.10"] 13 | os: [ubuntu-latest, macOS-latest, windows-latest] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install flit pytest 25 | - name: Install module 26 | run: | 27 | flit install --extras test 28 | - name: Run tests 29 | run: | 30 | pytest 31 | 32 | coverage: 33 | runs-on: ubuntu-latest 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: Set up Python ${{ matrix.python-version }} 38 | uses: actions/setup-python@v2 39 | with: 40 | python-version: ${{ matrix.python-version }} 41 | - name: Install dependencies 42 | run: | 43 | python -m pip install --upgrade pip 44 | pip install flit pytest coverage 45 | - name: Install module 46 | run: | 47 | flit install --extras test 48 | - name: Run tests 49 | run: | 50 | coverage run -m pytest 51 | coverage xml 52 | - name: Upload coverage to Codecov 53 | uses: codecov/codecov-action@v2 54 | with: 55 | verbose: true 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copied from bsoyka/template on GitHub 2 | # Some lines may not be necessary for this project 3 | 4 | #### PYTHON #### 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | 145 | #### VISUAL STUDIO CODE #### 146 | 147 | .vscode/* 148 | !.vscode/settings.json 149 | !.vscode/tasks.json 150 | !.vscode/launch.json 151 | !.vscode/extensions.json 152 | *.code-workspace 153 | 154 | # Local History for Visual Studio Code 155 | .history/ 156 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "littlefoxteam.vscode-python-test-adapter", 5 | "marvhen.reflow-markdown", 6 | "ms-python.python" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [ 3 | 80 4 | ], 5 | "reflowMarkdown.preferredLineLength": 80, 6 | "[python]": { 7 | "editor.rulers": [ 8 | 72, 9 | 79 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Thank you for choosing to contribute to Advent of Code® OCR! We welcome 4 | contributions of almost any kind, including ideas, bug reports, and code 5 | contributions. :tada: 6 | 7 | ## Contributing process 8 | 9 | 1. Start with an issue. Unless you're making an incredibly trivial fix, create 10 | an issue first. This allows us to review proposed changes before work is even 11 | started so no effort is wasted. 12 | - Wait until you're assigned to an issue before you start working on it. 13 | 2. Make your changes on a new branch in a fork of the repository. 14 | - Don't bump the version of the library. This will be done by a maintainer 15 | prior to releasing a new version. 16 | 3. Make sure your code works as intended by [testing it](#testing-code), and add 17 | new tests if necessary. 18 | 4. Commit your changes with a well written [commit message](#commit-messages). 19 | - Don't commit any changes to files that aren't related to the issue. 20 | 5. Push your changes to your fork and create a pull request. 21 | - Link the original issue in your pull request. 22 | 6. Wait for a maintainer to review your changes and give yourself a pat on the 23 | back! Just make sure to follow up on any comments or reviews you receive. 24 | 25 | ### Code style and tools 26 | 27 | We use an assortment of tools to ensure our code style remains consistent, to 28 | allow easier contributions, and to make sure our code works as expected. 29 | 30 | Run the following commands to ensure you have the latest versions of all the 31 | necessary development tools installed for this project: 32 | 33 | ```console 34 | pip install --upgrade black flit isort poethepoet 35 | poe install --extras test 36 | ``` 37 | 38 | No additional configuration is needed to use any of these tools once installed. 39 | Most commands you'll need to use can be run using Poe the Poet. 40 | 41 | #### Fomatting code 42 | 43 | To format Python code in the repository, run the following command: 44 | 45 | ```console 46 | poe format 47 | ``` 48 | 49 | This will format code and sort imports across the entire project. 50 | 51 | #### Installing AoC OCR locally 52 | 53 | To install Advent of Code OCR locally for development, run the following 54 | command: 55 | 56 | ```console 57 | poe install 58 | ``` 59 | 60 | This will add a symlink to your site packages directory linking to AoC OCR in 61 | this repository. 62 | 63 | #### Testing code 64 | 65 | To run all tests, use `tox` with the following command: 66 | 67 | ```conole 68 | poe test 69 | ``` 70 | 71 | This will run all tests across all the needed Python versions. To run only on 72 | your current version, use `pytest` alone: 73 | 74 | ```console 75 | pytest 76 | ``` 77 | 78 | ### Commit messages 79 | 80 | Commit messages are an important part of contributing to the project. They are 81 | crucial to tracking progress and ensuring that your work is accepted. 82 | 83 | Please follow these guidelines when writing a commit message: 84 | 85 | - Start your message with an appropriate emoji according to 86 | [Gitmoji](https://gitmoji.dev/). 87 | - Follow the emoji with one space and a short description of what you changed. 88 | - Start your message with a capital letter. 89 | - Use the imperative mood. (i.e. "Add this" rather than "Added this" or "Adding 90 | this") 91 | - Don't use puncuation at the end of your message. 92 | - If needed, add additional details in the body of your commit using proper 93 | English grammar and punctuation. 94 | 95 | Here's an example of a good commit message: 96 | 97 | > :sparkles: Add flag to open the first result in a browser 98 | 99 | ## Ideas and bug reports 100 | 101 | Sharing suggestions and reporting bugs are incredibly important parts of the 102 | open source workflow. 103 | 104 | Before opening an issue, please check over the open 105 | [issues](https://github.com/bsoyka/advent-of-code-ocr/issues) and 106 | [pull requests](https://github.com/bsoyka/advent-of-code-ocr/pulls) to see if 107 | your issue has already been addressed. 108 | 109 | Do not open issues or pull requests for security vulnerabilities. Instead, 110 | please see the [relevant section](#security-vulnerabilities) below for 111 | instructions. 112 | 113 | Here are some tips to write a great issue: 114 | 115 | - Be specific. 116 | - If you're reporting a bug, include detailed instructions to reproduce it. 117 | - Make sure you're using the latest version of the library to see if your issue 118 | or suggestion has already been resolved. 119 | - Only include one bug or suggestion per issue. Create additional issues for 120 | additional topics. 121 | 122 | ### Security vulnerabilities 123 | 124 | If you find a security issue in the AoC OCR library, please report it according 125 | to our [security policy](https://bsoyka.me/security) and do not make an issue or 126 | share the details publicly. 127 | 128 | ## Code of conduct 129 | 130 | By participating in this project and its community, you are expected to uphold 131 | our [code of conduct](https://bsoyka.me/conduct). 132 | 133 | If you have any questions or need to report a conduct issue, please email 134 | hello@bsoyka.me. 135 | 136 | --- 137 | 138 | Advent of Code is a registered trademark of Eric K Wastl in the United States. 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present Benjamin Soyka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code® OCR 2 | 3 | This Python module converts [Advent of Code](https://adventofcode.com/) ASCII 4 | art letters to plain characters. 5 | 6 | At the moment, it supports 6-pixel-tall characters as seen in 2016 Day 8, 2019 7 | Days 8 and 11, and 2021 Day 13. (Support for 10-pixel-tall characters, as seen 8 | in 2018 Day 10, is coming soon.) 9 | 10 | Put simply, it converts something like this to plain text: 11 | 12 | ```txt 13 | ██ ███ ██ 14 | █ █ █ █ █ █ 15 | █ █ ███ █ 16 | ████ █ █ █ 17 | █ █ █ █ █ █ 18 | █ █ ███ ██ 19 | ``` 20 | 21 | [![Downloads](https://pepy.tech/badge/advent-of-code-ocr)](https://pepy.tech/project/advent-of-code-ocr) 22 | [![Supported Versions](https://img.shields.io/pypi/pyversions/advent-of-code-ocr.svg)](https://pypi.org/project/advent-of-code-ocr) 23 | [![Testing](https://img.shields.io/github/workflow/status/bsoyka/advent-of-code-ocr/Test%20with%20pytest?label=tests)](https://github.com/bsoyka/advent-of-code-ocr/actions?query=workflow%3A%22Test+with+pytest%22) 24 | [![License](https://img.shields.io/pypi/l/advent-of-code-ocr)](https://github.com/bsoyka/advent-of-code-ocr/blob/master/LICENSE) 25 | [![Version](https://img.shields.io/pypi/v/advent-of-code-ocr?label=latest)](https://pypi.org/project/advent-of-code-ocr) 26 | 27 | ## Installation 28 | 29 | Advent of Code OCR is available on PyPI: 30 | 31 | ```sh 32 | $ pip install advent-of-code-ocr 33 | ``` 34 | 35 | Advent of Code OCR officially supports Python 3.7+. 36 | 37 | ## API Reference 38 | 39 | By default, this module recognizes `#` as a filled pixel and `.` as an empty 40 | pixel. However, you can change this using the `fill_pixel` and `empty_pixel` 41 | keywork arguments respectively. 42 | 43 | ```py 44 | from advent_of_code_ocr import convert_6 45 | 46 | print(convert_6(".##.\n#..#\n#..#\n####\n#..#\n#..#")) 47 | # A 48 | 49 | print(convert_6(" $$ \n$ $\n$ $\n$$$$\n$ $\n$ $", fill_pixel="$", empty_pixel=" ")) 50 | # A 51 | ``` 52 | 53 | You can also convert data that you have in a NumPy array or a nested list: 54 | 55 | ```py 56 | from advent_of_code_ocr import convert_array_6 57 | 58 | array = [ 59 | [0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 60 | [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1], 61 | [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0], 62 | [1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0], 63 | [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1], 64 | [1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 65 | ] 66 | print(convert_array_6(array, fill_pixel=1, empty_pixel=0)) 67 | # AOC 68 | ``` 69 | 70 | --- 71 | 72 | Advent of Code is a registered trademark of Eric K Wastl in the United States. 73 | -------------------------------------------------------------------------------- /advent_of_code_ocr/__init__.py: -------------------------------------------------------------------------------- 1 | """Convert Advent of Code ASCII art""" 2 | 3 | from __future__ import annotations 4 | 5 | from collections.abc import Sequence 6 | 7 | from .characters import ALPHABET_6 8 | 9 | __version__ = "1.0.0" 10 | 11 | 12 | def convert_6( 13 | input_text: str, *, fill_pixel: str = "#", empty_pixel: str = "." 14 | ) -> str: 15 | """Convert height 6 text to characters""" 16 | input_text = input_text.replace(fill_pixel, "#").replace(empty_pixel, ".") 17 | prepared_array = [list(line) for line in input_text.split("\n")] 18 | return _convert_6(prepared_array) 19 | 20 | 21 | def convert_array_6( 22 | array: Sequence[Sequence[str | int]], 23 | *, 24 | fill_pixel: str | int = "#", 25 | empty_pixel: str | int = "." 26 | ) -> str: 27 | """Convert a height 6 NumPy array or nested list to characters""" 28 | prepared_array = [ 29 | [ 30 | "#" if pixel == fill_pixel else "." if pixel == empty_pixel else "" 31 | for pixel in line 32 | ] 33 | for line in array 34 | ] 35 | return _convert_6(prepared_array) 36 | 37 | 38 | def _convert_6(array: list[list[str]]) -> str: 39 | """Convert a prepared height 6 array to characters""" 40 | # Validate input 41 | rows, cols = len(array), len(array[0]) 42 | if any(len(row) != cols for row in array): 43 | raise ValueError("all rows should have the same number of columns") 44 | if rows != 6: 45 | raise ValueError("incorrect number of rows (expected 6)") 46 | 47 | # Convert each letter 48 | indices = [slice(start, start + 4) for start in range(0, cols, 5)] 49 | result = [ 50 | ALPHABET_6["\n".join("".join(row[index]) for row in array)] 51 | for index in indices 52 | ] 53 | 54 | return "".join(result) 55 | -------------------------------------------------------------------------------- /advent_of_code_ocr/characters.py: -------------------------------------------------------------------------------- 1 | ALPHABET_6 = { 2 | ".##.\n#..#\n#..#\n####\n#..#\n#..#": "A", 3 | "###.\n#..#\n###.\n#..#\n#..#\n###.": "B", 4 | ".##.\n#..#\n#...\n#...\n#..#\n.##.": "C", 5 | "####\n#...\n###.\n#...\n#...\n####": "E", 6 | "####\n#...\n###.\n#...\n#...\n#...": "F", 7 | ".##.\n#..#\n#...\n#.##\n#..#\n.###": "G", 8 | "#..#\n#..#\n####\n#..#\n#..#\n#..#": "H", 9 | ".###\n..#.\n..#.\n..#.\n..#.\n.###": "I", 10 | "..##\n...#\n...#\n...#\n#..#\n.##.": "J", 11 | "#..#\n#.#.\n##..\n#.#.\n#.#.\n#..#": "K", 12 | "#...\n#...\n#...\n#...\n#...\n####": "L", 13 | ".##.\n#..#\n#..#\n#..#\n#..#\n.##.": "O", 14 | "###.\n#..#\n#..#\n###.\n#...\n#...": "P", 15 | "###.\n#..#\n#..#\n###.\n#.#.\n#..#": "R", 16 | ".###\n#...\n#...\n.##.\n...#\n###.": "S", 17 | "#..#\n#..#\n#..#\n#..#\n#..#\n.##.": "U", 18 | "#...\n#...\n.#.#\n..#.\n..#.\n..#.": "Y", 19 | "####\n...#\n..#.\n.#..\n#...\n####": "Z", 20 | } 21 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsoyka/advent-of-code-ocr/aae11d40720f0b681f2684b7aa4d25e3e0956b11/codecov.yml -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | ### BUILD CONFIG ### 2 | 3 | [build-system] 4 | build-backend = "flit_core.buildapi" 5 | requires = ["flit_core >=3.2,<4"] 6 | 7 | [project] 8 | authors = [{name = "Ben Soyka", email = "bensoyka@icloud.com"}] 9 | classifiers = [ 10 | "Development Status :: 5 - Production/Stable", 11 | "Intended Audience :: Developers", 12 | "Natural Language :: English", 13 | "License :: OSI Approved :: MIT License", 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.7", 17 | "Programming Language :: Python :: 3.8", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | ] 21 | dynamic = ["version", "description"] 22 | license = {file = "LICENSE"} 23 | name = "advent-of-code-ocr" 24 | readme = "README.md" 25 | requires-python = "~=3.7" 26 | 27 | [tool.flit.module] 28 | name = "advent_of_code_ocr" 29 | 30 | [project.urls] 31 | Changelog = "https://github.com/bsoyka/advent-of-code-ocr/releases" 32 | Source = "https://github.com/bsoyka/advent-of-code-ocr" 33 | 34 | [project.optional-dependencies] 35 | test = ["numpy", "pytest"] 36 | 37 | ### TOOLS ### 38 | 39 | [tool.black] 40 | line-length = 79 41 | 42 | [tool.coverage.run] 43 | source = ["advent_of_code_ocr"] 44 | 45 | [tool.coverage.report] 46 | omit = ["tests/*"] 47 | 48 | [tool.isort] 49 | line_length = 79 50 | profile = "black" 51 | 52 | [tool.poe.tasks] 53 | _black = "black ." 54 | _isort = "isort ." 55 | coverage = "coverage run -m pytest" 56 | format = ["_black", "_isort"] 57 | install = "flit install" 58 | test = "tox" 59 | 60 | [tool.pytest.ini_options] 61 | testpaths = ["tests"] 62 | 63 | [tool.tox] 64 | legacy_tox_ini = """ 65 | [tox] 66 | envlist = py37,py38,py39,py310 67 | isolated_build = True 68 | 69 | [testenv] 70 | deps = . 71 | commands = pytest 72 | extras = test 73 | """ 74 | -------------------------------------------------------------------------------- /tests/test_6.py: -------------------------------------------------------------------------------- 1 | from re import escape 2 | 3 | import numpy as np 4 | from pytest import mark, raises 5 | 6 | from advent_of_code_ocr import convert_6, convert_array_6 7 | from advent_of_code_ocr.characters import ALPHABET_6 8 | 9 | 10 | @mark.parametrize("test_input,expected", ALPHABET_6.items()) 11 | def test_single_letter(test_input, expected): 12 | assert convert_6(test_input) == expected 13 | 14 | 15 | @mark.parametrize( 16 | "test_input,expected", 17 | [ 18 | ( 19 | ".##..###...##.\n#..#.#..#.#..#\n#..#.###..#...\n####.#..#.#...\n#..#.#..#.#..#\n#..#.###...##.", 20 | "ABC", 21 | ) 22 | ], 23 | ) 24 | def test_three_letters(test_input, expected): 25 | assert convert_6(test_input) == expected 26 | 27 | 28 | @mark.parametrize( 29 | "test_input,fill_char,empty_char", 30 | [ 31 | ( 32 | "abbaabbbaaabba\nbaababaababaab\nbaababbbaabaaa\nbbbbabaababaaa\nbaababaababaab\nbaababbbaaabba", 33 | "b", 34 | "a", 35 | ), 36 | ( 37 | "!@@!!@@@!!!@@!\n@!!@!@!!@!@!!@\n@!!@!@@@!!@!!!\n@@@@!@!!@!@!!!\n@!!@!@!!@!@!!@\n@!!@!@@@!!!@@!", 38 | "@", 39 | "!", 40 | ), 41 | ], 42 | ) 43 | def test_different_characters(test_input, fill_char, empty_char): 44 | assert ( 45 | convert_6(test_input, fill_pixel=fill_char, empty_pixel=empty_char) 46 | == "ABC" 47 | ) 48 | 49 | 50 | def test_long_string(): 51 | # Split into list of lines for readability/formatting 52 | string = "\n".join( 53 | [ 54 | ".##..###...##..####.####..##..#..#..###...##.#..#.#.....##..###..###...###.#..#.#...#####", 55 | "#..#.#..#.#..#.#....#....#..#.#..#...#.....#.#.#..#....#..#.#..#.#..#.#....#..#.#...#...#", 56 | "#..#.###..#....###..###..#....####...#.....#.##...#....#..#.#..#.#..#.#....#..#..#.#...#.", 57 | "####.#..#.#....#....#....#.##.#..#...#.....#.#.#..#....#..#.###..###...##..#..#...#...#..", 58 | "#..#.#..#.#..#.#....#....#..#.#..#...#..#..#.#.#..#....#..#.#....#.#.....#.#..#...#..#...", 59 | "#..#.###...##..####.#.....###.#..#..###..##..#..#.####..##..#....#..#.###...##....#..####", 60 | ] 61 | ) 62 | assert convert_6(string) == "ABCEFGHIJKLOPRSUYZ" 63 | 64 | 65 | @mark.parametrize("rows", [0, 1, 5, 7, 10]) 66 | def test_number_of_rows(rows): 67 | with raises( 68 | ValueError, match=escape("incorrect number of rows (expected 6)") 69 | ): 70 | convert_6("\n".join("" for _ in range(rows))) 71 | 72 | 73 | def test_strange_width_characters(): 74 | # Split into list of lines for readability/formatting 75 | string = "\n".join( 76 | [ 77 | "####.####.####.#...##..#.####.###..####..###...##.", 78 | "#....#....#....#...##.#..#....#..#.#......#.....#.", 79 | "###..###..###...#.#.##...###..#..#.###....#.....#.", 80 | "#....#....#......#..#.#..#....###..#......#.....#.", 81 | "#....#....#......#..#.#..#....#.#..#......#..#..#.", 82 | "####.#....####...#..#..#.#....#..#.#.....###..##..", 83 | ] 84 | ) 85 | assert convert_6(string) == "EFEYKFRFIJ" 86 | 87 | 88 | def test_array_nested_list(): 89 | array = [ 90 | ["X", "O", "O", "X", "X", "X", "O", "O", "X"], 91 | ["O", "X", "X", "O", "X", "O", "X", "X", "O"], 92 | ["O", "X", "X", "O", "X", "O", "X", "X", "X"], 93 | ["O", "O", "O", "O", "X", "O", "X", "X", "X"], 94 | ["O", "X", "X", "O", "X", "O", "X", "X", "O"], 95 | ["O", "X", "X", "O", "X", "X", "O", "O", "X"], 96 | ] 97 | assert convert_array_6(array, fill_pixel="O", empty_pixel="X") == "AC" 98 | 99 | 100 | def test_array_list_of_strings(): 101 | array = [ 102 | ".oo...oo..oooo..ooo.o..o..ooo..oo..o..o", 103 | "o..o.o..o.o....o....o..o...o..o..o.o..o", 104 | "o..o.o....ooo..o....oooo...o..o....oooo", 105 | "oooo.o....o.....oo..o..o...o..o.oo.o..o", 106 | "o..o.o..o.o.......o.o..o...o..o..o.o..o", 107 | "o..o..oo..oooo.ooo..o..o..ooo..ooo.o..o", 108 | ] 109 | assert convert_array_6(array, fill_pixel="o") == "ACESHIGH" 110 | 111 | 112 | def test_array_numpy(): 113 | array = np.array( 114 | [ 115 | [0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 116 | [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1], 117 | [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0], 118 | [1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0], 119 | [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1], 120 | [1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 121 | ] 122 | ) 123 | assert convert_array_6(array, fill_pixel=1, empty_pixel=0) == "AOC" 124 | 125 | 126 | @mark.parametrize("rows", [1, 5, 7, 10]) 127 | def test_array_number_of_rows(rows): 128 | with raises( 129 | ValueError, match=escape("incorrect number of rows (expected 6)") 130 | ): 131 | convert_array_6(["" for _ in range(rows)]) 132 | --------------------------------------------------------------------------------