Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
43fc5e7
feat: implement code
PythonProjectDeveloper Apr 28, 2019
729d119
fix: change setup.py
PythonProjectDeveloper Apr 28, 2019
721d116
fix: change setup.py
PythonProjectDeveloper Apr 28, 2019
775cccc
fix: change setup.py
PythonProjectDeveloper Apr 28, 2019
c1d5a20
fix: change setup.py
PythonProjectDeveloper Apr 28, 2019
89b8d2a
fix: change setup.py
PythonProjectDeveloper Apr 28, 2019
1087473
refactor: make regular expressions in separate file
PythonProjectDeveloper Apr 28, 2019
3c8094b
fix: return to its former state
PythonProjectDeveloper Apr 28, 2019
a33d56b
refactor: make regular expressions in separate file
PythonProjectDeveloper May 12, 2019
2c0dbec
refactor: carry out operators in the dictionary
PythonProjectDeveloper May 12, 2019
308bc29
refactor: change file name math_regexp to regexp
PythonProjectDeveloper May 12, 2019
bc75852
refactor: split code into multiple files
PythonProjectDeveloper May 12, 2019
cdb9a8f
refactor: change style of code to pycodestyle
PythonProjectDeveloper May 12, 2019
3d20b3a
docs: add docs for files
PythonProjectDeveloper May 12, 2019
0d4c494
refactor: rename variables
PythonProjectDeveloper May 13, 2019
d7abb7f
feat: update setup.py
PythonProjectDeveloper May 13, 2019
b64f2ae
refactor: add blank lines and __init__ file
PythonProjectDeveloper May 13, 2019
21fccf7
refactor: change style of file import
PythonProjectDeveloper May 13, 2019
f3ed670
refactor: remove unnecessary imports
PythonProjectDeveloper May 13, 2019
7412ff4
refactor: decompose files into folders
PythonProjectDeveloper May 13, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
7 changes: 7 additions & 0 deletions final_task/calculator/checker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .checker import (
check_spaces,
check_brackets,
check_constant,
check_function,
check_expression,
)
142 changes: 142 additions & 0 deletions final_task/calculator/checker/checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""
The module is designed to test the mathematical expression for correctness.

Example:
check_spaces(' 1+ 3 / 2')
>>> '1+3/2'

check_brackets('2*(3+5)')
>>> '2*(3+5)'

lib = {
'e': 2.718281828459045,
'sum': sum
}
check_function('sum(100, 50)', lib)
>>> 'sum(100, 50)'
"""

import re
from ..library import Library
from ..operators import (
LEFT_BRACKET,
RIGHT_BRACKET,
)
from ..regexp import (
REGEXP_INCORECT_EXPRETION,
REGEXP_CONSTANT,
REGEXP_DIGIT,
REGEXP_FUNCTION,
)


def check_spaces(expr: str) -> str:
"""
Checks if an expression has the wrong elements.

Args:
expr (str): String mathematical expression.

Returns:
str: cleared expression from spaces.

Raises:
ValueError: If `expr` is not correct`.
"""
matches = re.findall(REGEXP_INCORECT_EXPRETION, expr)
if matches:
raise ValueError('expression is not correct')

return expr.replace(' ', '')


def check_brackets(expr: str):
"""
Checks if all brackets have a pair.

Args:
expr (str): String mathematical expression.

Raises:
ValueError: If `expr` is not correct`.
"""
stack = []
for symbol in expr:
if symbol == LEFT_BRACKET:
stack.append(symbol)
elif symbol == RIGHT_BRACKET and (not stack or stack.pop() != LEFT_BRACKET):
raise ValueError('brackets are not balanced')

if stack:
raise ValueError('brackets are not balanced')


def check_constant(expr: str, library: Library):
"""
Checks if all constants in the expression are available.

Args:
expr (str): String mathematical expression.
library (Library): dictionary of functions and constant.

Raises:
ValueError: If `expr` is not correct`.
"""
matches = re.finditer(REGEXP_CONSTANT, expr)
for match in matches:
name = match.group('name')

if name[-1] == LEFT_BRACKET or re.match(REGEXP_DIGIT, name):
continue

if name[0].isdigit():
raise ValueError(f'constant {name} can not start with digit')

if name not in library or callable(library[name]):
raise ValueError(f'there is no such constant {name}')


def check_function(expr: str, library: Library):
"""
Checks if all functions in the expression are available.

Args:
expr (str): String mathematical expression.
library (Library): dictionary of functions and constant.

Raises:
ValueError: If `expr` is not correct`.
"""
matches = re.finditer(REGEXP_FUNCTION, expr)
for match in matches:
name = match.group('name')
pattern = match.group('pattern')

if name[0].isdigit():
raise ValueError(f'function {pattern} can not start with digit')

if name not in library or not callable(library[name]):
raise ValueError(f'there is no such function {pattern}')


def check_expression(expr: str, library: Library) -> str:
"""
Checks the expression for correctness.

Args:
expr (str): String mathematical expression.
library (Library): dictionary of functions and constant.

Returns:
str: cleared expression.

Raises:
ValueError: If `expr` is not correct`.
"""
expr = check_spaces(expr)
check_brackets(expr)
check_constant(expr, library)
check_function(expr, library)

return expr
115 changes: 115 additions & 0 deletions final_task/calculator/checker/test_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import unittest
from ..library import Library
from .checker import (
check_brackets,
check_constant,
check_expression,
check_function,
check_spaces,
)


class TestCheckFunction(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.lib = Library('math')

def test_check_spaces(self):
with self.subTest("Throws error if spaces is not correct"):
self.assertRaises(ValueError, lambda: check_spaces(''))
self.assertRaises(ValueError, lambda: check_spaces('------'))
self.assertRaises(ValueError, lambda: check_spaces('-'))
self.assertRaises(ValueError, lambda: check_spaces('+'))
self.assertRaises(ValueError, lambda: check_spaces('1-'))
self.assertRaises(ValueError, lambda: check_spaces('1 + 1 2 3 4 5 6 '))
self.assertRaises(ValueError, lambda: check_spaces('* *'))
self.assertRaises(ValueError, lambda: check_spaces('/ /'))
self.assertRaises(ValueError, lambda: check_spaces('/ *'))
self.assertRaises(ValueError, lambda: check_spaces('+ *'))
self.assertRaises(ValueError, lambda: check_spaces('1 2'))
self.assertRaises(ValueError, lambda: check_spaces('= ='))
self.assertRaises(ValueError, lambda: check_spaces('! ='))
self.assertRaises(ValueError, lambda: check_spaces('<-+!'))
self.assertRaises(ValueError, lambda: check_spaces('==7'))
self.assertRaises(ValueError, lambda: check_spaces('1 + 2(3 * 4))'))
self.assertRaises(ValueError, lambda: check_spaces('1 = = 2'))
self.assertRaises(ValueError, lambda: check_spaces('1<>2'))
self.assertRaises(ValueError, lambda: check_spaces('1><2'))

with self.subTest("removes spaces and returns new expretion"):
self.assertEqual(check_spaces('1 + 2'), '1+2')
self.assertEqual(check_spaces('1-2'), '1-2')
self.assertEqual(check_spaces('1 * - 2'), '1*-2')
self.assertEqual(check_spaces('1 == 2'), '1==2')
self.assertEqual(check_spaces('1 <= 2'), '1<=2')
self.assertEqual(check_spaces('1 - sin (1, 2, 3) + - 2'), '1-sin(1,2,3)+-2')
self.assertEqual(check_spaces('sin(pi/2)'), 'sin(pi/2)')
self.assertTrue(check_spaces('sin(e^log(e^e^sin(23.0),45.0)+cos(3.0+log10(e^-e)))'))
self.assertTrue(check_spaces('time()-e-(1+1)/60+1-1*1//10000%1000^2==1==1<=3>=5<1>1'))

val = ('sin(-cos(-sin(3.0)-cos(-sin(-3.0*5.0)-sin(cos(log10(43.0))))'
'+cos(sin(sin(34.0-2.0^2.0))))--cos(1.0)--cos(0.0)^3.0)')
self.assertTrue(check_spaces(val))

def test_check_brackets(self):
with self.subTest("Throws error if brackets are not unpaired"):
self.assertRaises(ValueError, lambda: check_brackets('('))
self.assertRaises(ValueError, lambda: check_brackets(')'))
self.assertRaises(ValueError, lambda: check_brackets('())('))
self.assertRaises(ValueError, lambda: check_brackets('(()))'))
self.assertRaises(ValueError, lambda: check_brackets(')()('))

with self.subTest("returns nothing if expretion is good"):
self.assertIsNone(check_brackets(''))
self.assertIsNone(check_brackets('()'))
self.assertIsNone(check_brackets('((()))()'))
self.assertIsNone(check_brackets('()()()()'))
self.assertIsNone(check_brackets('(()(()())())'))

def test_check_constant(self):
with self.subTest("Throws error if environment does not have constant"):
self.assertRaises(ValueError, lambda: check_constant('constant', self.lib))
self.assertRaises(ValueError, lambda: check_constant('constant + 5', self.lib))
self.assertRaises(ValueError, lambda: check_constant('sin(1) + constant + 7', self.lib))

with self.subTest("Throws error if constant name starts with digit"):
self.assertRaises(ValueError, lambda: check_constant('10constant', self.lib))
self.assertRaises(ValueError, lambda: check_constant('10constant + 5', self.lib))
self.assertRaises(ValueError, lambda: check_constant('sin(1) + 10constant + 7', self.lib))

with self.subTest("returns nothing if expretion is good"):
self.assertIsNone(check_constant('', self.lib))
self.assertIsNone(check_constant('e', self.lib))
self.assertIsNone(check_constant('sin(21)', self.lib))
self.assertIsNone(check_constant('sin(21) + e', self.lib))
self.assertIsNone(check_constant('2.4178516392292583e+24 + 5', self.lib))

def test_check_function(self):
with self.subTest("Throws error if environment does not have function"):
self.assertRaises(ValueError, lambda: check_function('multiply()', self.lib))
self.assertRaises(ValueError, lambda: check_function('multiply(5,7)', self.lib))
self.assertRaises(ValueError, lambda: check_function('multiply() + 7', self.lib))

with self.subTest("Throws error if function name starts with digit"):
self.assertRaises(ValueError, lambda: check_function('10log()', self.lib))
self.assertRaises(ValueError, lambda: check_function('10log(1)', self.lib))
self.assertRaises(ValueError, lambda: check_function('10log(5,7)', self.lib))
self.assertRaises(ValueError, lambda: check_function('10log() + 7', self.lib))

with self.subTest("returns nothing if expretion is good"):
self.assertIsNone(check_function('', self.lib))
self.assertIsNone(check_function('e', self.lib))
self.assertIsNone(check_function('sin(21)', self.lib))
self.assertIsNone(check_function('sin(21) + e', self.lib))
self.assertIsNone(check_function('2.4178516392292583e+24 + 5', self.lib))

def test_check_expression(self):
with self.subTest("returns expression without spaces"):
self.assertEqual(check_expression('1 + 2', self.lib), '1+2')
self.assertEqual(check_expression('1-2', self.lib), '1-2')
self.assertEqual(check_expression('1 * - 2', self.lib), '1*-2')
self.assertEqual(check_expression('1 - sin (1, 2, 3) + - 2', self.lib), '1-sin(1,2,3)+-2')


if __name__ == '__main__':
unittest.main()
1 change: 1 addition & 0 deletions final_task/calculator/converter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .converter import convert_answer
36 changes: 36 additions & 0 deletions final_task/calculator/converter/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
"""
The module is designed to convert the expression to the desired type.

Example:
convert_answer('-1', False)
>>> '-1'

convert_answer('-1', False)
>>> '0'

convert_answer('-1', True)
>>> 'True'

convert_answer('0', True)
>>> 'False'
"""

from ..regexp import has_non_zero_fraction_part


def convert_answer(expr: str, has_compare: bool) -> str:
"""
Converts the resulting string to the desired type.

Args:
expr (str): String representation of a number.
has_compare (bool): whether the expression contains boolean logic
"""
num = float(expr)
match = has_non_zero_fraction_part(expr)
num = num if match else int(num)

result = bool(num) if has_compare else num

return str(result)
15 changes: 15 additions & 0 deletions final_task/calculator/converter/test_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import unittest
from .converter import convert_answer


class TestConverterFunction(unittest.TestCase):
def test_convert_answer(self):
with self.subTest("returns correct answer"):
self.assertEqual(convert_answer('-1', False), '-1')
self.assertEqual(convert_answer('0', False), '0')
self.assertEqual(convert_answer('-1', True), 'True')
self.assertEqual(convert_answer('0', True), 'False')


if __name__ == '__main__':
unittest.main()
1 change: 1 addition & 0 deletions final_task/calculator/library/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .library import Library
33 changes: 33 additions & 0 deletions final_task/calculator/library/library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
"""
The module is designed for dynamic creation of module libraries.

Example:
lib = Library('math', 'os')
lib.update('time', 'os')

lib['e']
>>> 2.718281828459045

lib['sum'](5, 10)
>>> 15
"""


class Library(dict):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использование класса с родителем dict в этом случае выглядит слишком сложным способом решния нужной задачи. Можно обойтись обычной функцией, которая будет возвращаться нужный словарь.

"""
Class is designed to work with modules.
It is a dictionary of functions and constants.
"""
def __init__(self, *modules: list):
super().__init__()

self['abs'] = abs
self['round'] = round

self.update(*modules)

def update(self, *modules: list):
"""Adds functions and veriables from got module names to dictionary."""
for module in modules:
super().update(__import__(module).__dict__)
38 changes: 38 additions & 0 deletions final_task/calculator/library/test_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import unittest
from .library import Library


class TestLibraryClass(unittest.TestCase):
def test__init__(self):
with self.subTest("contains around and abs functon by default"):
lib = Library()

self.assertTrue('round' in lib)
self.assertTrue('abs' in lib)

with self.subTest("can get module names and adds their variables to your own dictionary"):
lib = Library('math', 'os', 'time')

self.assertTrue('path' in lib)
self.assertTrue('clock' in lib)
self.assertTrue('pi' in lib)

def test_update(self):
with self.subTest("get module names and adds their variables to your own dictionary"):
lib = Library()
self.assertFalse('path' in lib)

lib.update('os')
self.assertTrue('path' in lib)

lib.update('sys', 'time')
self.assertTrue('stdin' in lib)
self.assertTrue('clock' in lib)

with self.subTest("raises error if veriable is not found"):
self.assertRaises(ModuleNotFoundError, lambda: lib.update('bad_module'))
self.assertRaises(ModuleNotFoundError, lambda: lib.update('new_math'))


if __name__ == '__main__':
unittest.main()
Loading