From 8728366e8f49da36cd4f3798b5c5d5ae848aac46 Mon Sep 17 00:00:00 2001 From: CherryKitten Date: Fri, 25 Nov 2022 21:59:31 +0100 Subject: [PATCH] Add Arithmetic formatter --- .../1-arithmetic-formatter/.replit | 2 + .../1-arithmetic-formatter/README.md | 3 + .../arithmetic_arranger.cpython-311.pyc | Bin 0 -> 2641 bytes .../test_module.cpython-311-pytest-6.2.5.pyc | Bin 0 -> 5178 bytes .../arithmetic_arranger.py | 40 +++++ .../1-arithmetic-formatter/main.py | 11 ++ .../1-arithmetic-formatter/poetry.lock | 154 ++++++++++++++++++ .../1-arithmetic-formatter/pyproject.toml | 15 ++ .../1-arithmetic-formatter/replit.nix | 19 +++ .../1-arithmetic-formatter/test_module.py | 77 +++++++++ 10 files changed, 321 insertions(+) create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/.replit create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/README.md create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/__pycache__/arithmetic_arranger.cpython-311.pyc create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/__pycache__/test_module.cpython-311-pytest-6.2.5.pyc create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/arithmetic_arranger.py create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/main.py create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/poetry.lock create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/pyproject.toml create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/replit.nix create mode 100644 8-scientific-computing-python/1-arithmetic-formatter/test_module.py diff --git a/8-scientific-computing-python/1-arithmetic-formatter/.replit b/8-scientific-computing-python/1-arithmetic-formatter/.replit new file mode 100644 index 0000000..c88f45f --- /dev/null +++ b/8-scientific-computing-python/1-arithmetic-formatter/.replit @@ -0,0 +1,2 @@ +language = "python3" +run = "python main.py" \ No newline at end of file diff --git a/8-scientific-computing-python/1-arithmetic-formatter/README.md b/8-scientific-computing-python/1-arithmetic-formatter/README.md new file mode 100644 index 0000000..22bff30 --- /dev/null +++ b/8-scientific-computing-python/1-arithmetic-formatter/README.md @@ -0,0 +1,3 @@ +# Arithmetic Formatter + +This is the boilerplate for the Arithmetic Formatter project. Instructions for building your project can be found at https://www.freecodecamp.org/learn/scientific-computing-with-python/scientific-computing-with-python-projects/arithmetic-formatter diff --git a/8-scientific-computing-python/1-arithmetic-formatter/__pycache__/arithmetic_arranger.cpython-311.pyc b/8-scientific-computing-python/1-arithmetic-formatter/__pycache__/arithmetic_arranger.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..696c32f200534d2c68a11081302213cf0578f55b GIT binary patch literal 2641 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 5: + return "Error: Too many problems." + + for problem in problems: + op = re.search('\\s[+-]\\s', problem) + if op is None: + return "Error: Operator must be '+' or '-'." + op = op.group().strip() + problem = problem.split(op) + a, b = problem[0].strip(), problem[1].strip() + if not a.isdigit() or not b.isdigit(): return "Error: Numbers must only contain digits." + if len(a) > 4 or len(b) > 4: return "Error: Numbers cannot be more than four digits." + if len(line1) > 0: + line1 = line1 + 4 * " " + line2 = line2 + 4 * " " + line3 = line3 + 4 * " " + pad = len(a) + 2 if len(a) > len(b) else len(b) + 2 + line1 = line1 + a.rjust(pad, " ") + line2 = line2 + op + (pad - 1 - len(b)) * " " + b + line3 = line3 + pad * "-" + if solve is True: + if len(line4) > 0: + line4 = line4 + 4 * " " + if op == "+": + x = int(a) + int(b) + line4 = line4 + str(x).rjust(pad, " ") + else: + x = int(a) - int(b) + line4 = line4 + str(x).rjust(pad, " ") + arranged_problems = line1 + '\n' + line2 + '\n' + line3 + if len(line4) > 0: arranged_problems = arranged_problems + '\n' + line4 + return arranged_problems diff --git a/8-scientific-computing-python/1-arithmetic-formatter/main.py b/8-scientific-computing-python/1-arithmetic-formatter/main.py new file mode 100644 index 0000000..e18806f --- /dev/null +++ b/8-scientific-computing-python/1-arithmetic-formatter/main.py @@ -0,0 +1,11 @@ +# This entrypoint file to be used in development. Start by reading README.md +from pytest import main + +from arithmetic_arranger import arithmetic_arranger + + +print(arithmetic_arranger(["32 + 698", "3801 - 2", "45 + 43", "123 + 49"], True)) + + +# Run unit tests automatically +main(['-vv']) diff --git a/8-scientific-computing-python/1-arithmetic-formatter/poetry.lock b/8-scientific-computing-python/1-arithmetic-formatter/poetry.lock new file mode 100644 index 0000000..448208a --- /dev/null +++ b/8-scientific-computing-python/1-arithmetic-formatter/poetry.lock @@ -0,0 +1,154 @@ +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "b0cfcbac06077cd402c6bf0fdbc3a1f9250d0198334abbec48a77f542707e82a" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] diff --git a/8-scientific-computing-python/1-arithmetic-formatter/pyproject.toml b/8-scientific-computing-python/1-arithmetic-formatter/pyproject.toml new file mode 100644 index 0000000..e17ec99 --- /dev/null +++ b/8-scientific-computing-python/1-arithmetic-formatter/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "fcc-arithmetic-formatter" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.8" +pytest = "^6.2.4" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/8-scientific-computing-python/1-arithmetic-formatter/replit.nix b/8-scientific-computing-python/1-arithmetic-formatter/replit.nix new file mode 100644 index 0000000..6a4bee2 --- /dev/null +++ b/8-scientific-computing-python/1-arithmetic-formatter/replit.nix @@ -0,0 +1,19 @@ +{ pkgs }: { + deps = [ + pkgs.python38Full + pkgs.python38Packages.pytest + ]; + env = { + PYTHON_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ + # Needed for pandas / numpy + pkgs.stdenv.cc.cc.lib + pkgs.zlib + # Needed for pygame + pkgs.glib + # Needed for matplotlib + pkgs.xorg.libX11 + ]; + PYTHONBIN = "${pkgs.python38Full}/bin/python3.8"; + LANG = "en_US.UTF-8"; + }; +} \ No newline at end of file diff --git a/8-scientific-computing-python/1-arithmetic-formatter/test_module.py b/8-scientific-computing-python/1-arithmetic-formatter/test_module.py new file mode 100644 index 0000000..8118131 --- /dev/null +++ b/8-scientific-computing-python/1-arithmetic-formatter/test_module.py @@ -0,0 +1,77 @@ +import pytest + +from arithmetic_arranger import arithmetic_arranger + +test_cases = [ + pytest.param( + [['3801 - 2', '123 + 49']], + ' 3801 123\n' + '- 2 + 49\n' + '------ -----', + 'Expected different output when calling "arithmetic_arranger()" with ["3801 - 2", "123 + 49"]', + id='test_two_problems_arrangement1'), + pytest.param( + [['1 + 2', '1 - 9380']], + ' 1 1\n' + '+ 2 - 9380\n' + '--- ------', + 'Expected different output when calling "arithmetic_arranger()" with ["1 + 2", "1 - 9380"]', + id='test_two_problems_arrangement2'), + pytest.param( + [['3 + 855', '3801 - 2', '45 + 43', '123 + 49']], + ' 3 3801 45 123\n' + '+ 855 - 2 + 43 + 49\n' + '----- ------ ---- -----', + 'Expected different output when calling "arithmetic_arranger()" with ["3 + 855", "3801 - 2", "45 + 43", "123 + 49"]', + id='test_four_problems_arrangement'), + pytest.param( + [['11 + 4', '3801 - 2999', '1 + 2', '123 + 49', '1 - 9380']], + ' 11 3801 1 123 1\n' + '+ 4 - 2999 + 2 + 49 - 9380\n' + '---- ------ --- ----- ------', + 'Expected different output when calling "arithmetic_arranger()" with ["11 + 4", "3801 - 2999", "1 + 2", "123 + 49", "1 - 9380"]', + id='test_five_problems_arrangement'), + pytest.param( + [['44 + 815', '909 - 2', '45 + 43', '123 + 49', + '888 + 40', '653 + 87']], + 'Error: Too many problems.', + 'Expected calling "arithmetic_arranger()" with more than five problems to return "Error: Too many problems."', + id='test_too_many_problems'), + pytest.param( + [['3 / 855', '3801 - 2', '45 + 43', '123 + 49']], + "Error: Operator must be '+' or '-'.", + '''Expected calling "arithmetic_arranger()" with a problem that uses the "/" operator to return "Error: Operator must be '+' or '-'."''', + id='test_incorrect_operator'), + pytest.param( + [['24 + 85215', '3801 - 2', '45 + 43', '123 + 49']], + 'Error: Numbers cannot be more than four digits.', + 'Expected calling "arithmetic_arranger()" with a problem that has a number over 4 digits long to return "Error: Numbers cannot be more than four digits."', + id='test_too_many_digits'), + pytest.param( + [['98 + 3g5', '3801 - 2', '45 + 43', '123 + 49']], + 'Error: Numbers must only contain digits.', + 'Expected calling "arithmetic_arranger()" with a problem that contains a letter character in the number to return "Error: Numbers must only contain digits."', + id='test_only_digits'), + pytest.param( + [['3 + 855', '988 + 40'], True], + ' 3 988\n' + '+ 855 + 40\n' + '----- -----\n' + ' 858 1028', + 'Expected solutions to be correctly displayed in output when calling "arithmetic_arranger()" with ["3 + 855", "988 + 40"] and a second argument of `True`.', + id='test_two_problems_with_solutions'), + pytest.param( + [['32 - 698', '1 - 3801', '45 + 43', '123 + 49', '988 + 40'], True], + ' 32 1 45 123 988\n' + '- 698 - 3801 + 43 + 49 + 40\n' + '----- ------ ---- ----- -----\n' + ' -666 -3800 88 172 1028', + 'Expected solutions to be correctly displayed in output when calling "arithmetic_arranger()" with five arithmetic problems and a second argument of `True`.', + id='test_five_problems_with_solutions'), +] + + +@pytest.mark.parametrize('arguments,expected_output,fail_message', test_cases) +def test_template(arguments, expected_output, fail_message): + actual = arithmetic_arranger(*arguments) + assert actual == expected_output, fail_message