-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Metelskiy Ivan #14
base: master
Are you sure you want to change the base?
Metelskiy Ivan #14
Changes from 12 commits
60f1f56
a47ee67
f7871af
ee72bda
f192457
636907d
ff28467
e5431e8
390dca1
054e50e
6ed0fc0
53f55ac
d5104ad
a76e83e
c2d6bd1
7895f07
57dd907
c6612a2
7549c7e
113131b
55014af
0af8efe
5123adc
270bb4b
8c636d6
d587c8b
5f759b5
3ee0740
fc64eba
0e5d8f6
6a70980
fadddfb
d5300d1
115090c
9c69bba
1b0cdbd
3e5a015
f3ab1ec
058e12d
0013b8a
4b8584d
bcd7395
ff2ef4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,6 @@ venv.bak/ | |
|
||
# mypy | ||
.mypy_cache/ | ||
|
||
#pycharm | ||
.idea/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
""" | ||
|
||
module which contains base constant values which uses for calculating expressions | ||
|
||
""" | ||
|
||
import re | ||
import operator | ||
|
||
operator = { | ||
'+': operator.add, | ||
'-': operator.sub, | ||
'*': operator.mul, | ||
'/': operator.truediv, | ||
'%': operator.mod, | ||
'^': operator.pow, | ||
'//': operator.floordiv, | ||
'==': operator.eq, | ||
'<=': operator.le, | ||
'>=': operator.ge, | ||
'>': operator.gt, | ||
'<': operator.lt, | ||
'!=': operator.ne | ||
} | ||
|
||
lowest_priority_operator = '(' | ||
low_priority_operators = ('<', '==', '!=', '>=', '>') | ||
mid_priority_operators = ('+', '-') | ||
high_priority_operators = ('*', '/', '%', '//') | ||
highest_priority_operator = '^' | ||
|
||
LOWEST_PRIORITY = 0 | ||
LOW_PRIORITY = 1 | ||
MID_PRIORITY = 2 | ||
HIGH_PRIORITY = 3 | ||
HIGHEST_PRIORITY = 4 | ||
|
||
RE_MAIN_PARSE_ARG = re.compile(r'\d+[.]\d+|\d+|[+,\-*^]|[/=!<>]+|\w+|[()]') | ||
# | ||
RE_NEGATIVE_VALUES = re.compile(r'[^\w)]+[\-]\d+[.]\d+|[^\w)]+[\-]\d+') | ||
# | ||
RE_NEGATIVE_VALUES_ON_STR_BEG = re.compile(r'^-\d+[.]\d+|^-\w+') | ||
# | ||
RE_INTS = re.compile(r'\d+') | ||
# | ||
RE_FLOATS = re.compile(r'\d+[.]\d+') | ||
# | ||
RE_FUNCTIONS = re.compile(r'[a-zA-Z]+[0-9]+|[a-zA-Z]+') | ||
# | ||
RE_OPERATIONS = re.compile(r'[(<=!>+\-*/%^]+') | ||
# | ||
RE_NEGATIVE_FUNCS = re.compile(r'[^\w)]-\w+[(].+[)]') | ||
|
||
imports = ['math'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,309 @@ | ||
""" | ||
|
||
module which contains methods which make up base logic of the calculator | ||
|
||
""" | ||
import re | ||
import inspect | ||
import importlib | ||
import constants | ||
import pycodestyle | ||
from typing import Any | ||
|
||
pycodestyle.maximum_line_length = 120 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. в данном случае лучше передавать такие настройки напрямую утилите |
||
|
||
|
||
def parse_funcs_params(ex_list: list, methods: dict)-> list: | ||
if not ex_list: | ||
return None | ||
scobes_count = 0 | ||
found_func = False | ||
output_list = [] | ||
params_list = [] | ||
params_buff_list = [] | ||
one_param_list = [] | ||
for ex in ex_list: | ||
if constants.RE_FUNCTIONS.findall(ex) and callable(methods[ex]): | ||
if not found_func: | ||
found_func = True | ||
output_list.append(ex) | ||
else: | ||
one_param_list.append(ex) | ||
continue | ||
if ex == '(': | ||
if not scobes_count: | ||
output_list.append(ex) | ||
else: | ||
one_param_list.append(ex) | ||
scobes_count += 1 | ||
continue | ||
if ex == ')': | ||
scobes_count -= 1 | ||
if not scobes_count: | ||
found_func = False | ||
params_list.append(one_param_list) | ||
one_param_list = [] | ||
for list in params_list: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
params_buff_list.append(parse_funcs_params(list, methods)) | ||
params_list = params_buff_list | ||
output_list.append(params_list) | ||
params_list = [] | ||
params_buff_list = [] | ||
output_list.append(ex) | ||
else: | ||
one_param_list.append(ex) | ||
continue | ||
if ex == ',' and scobes_count == 1: | ||
params_list.append(one_param_list) | ||
one_param_list = [] | ||
continue | ||
if scobes_count: | ||
one_param_list.append(ex) | ||
continue | ||
output_list.append(ex) | ||
|
||
return output_list | ||
|
||
|
||
def import_usr_imports(usr_imports: list): | ||
"""import user files | ||
|
||
function gets objects of user imports and added it to global namespace | ||
|
||
:param usr_imports: bunch of user imports names | ||
:return: nothing | ||
""" | ||
import_objects = dict() | ||
|
||
for import_elem in usr_imports: | ||
import_objects[import_elem] = importlib.import_module(import_elem) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. import_objects = {import_elem: importlib.import_module(import_elem) for import_elem in usr_imports} |
||
|
||
globals().update(import_objects) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Опасный подход с хранением импортированных объектов напрямую в глобальной секции. Намного лучше будет хранить эти объекты в отдельном словаре (либо что-то другое, но точно не стоит хранит это в globals) |
||
|
||
|
||
def _get_item_by_type(item: str, methods: dict) -> Any: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Одна точка выхода из функции обычно лучше, чем несколько. В данном случае можно сделать только один |
||
"""get item by its string according its type | ||
|
||
:param item: string of equivalent of some number | ||
:param methods: dictionary which contains objects of methods of imports | ||
:return: number by its type | ||
""" | ||
if not type(item) is str: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if not isinstance(item, str) |
||
return item | ||
if constants.RE_FLOATS.findall(item): | ||
return float(item) | ||
elif constants.RE_INTS.findall(item): | ||
return int(item) | ||
elif constants.RE_FUNCTIONS.findall(item) and not callable(methods[item]): | ||
return methods[item] | ||
else: | ||
return None | ||
|
||
|
||
def get_imports_attrs(user_imports: list) -> dict: | ||
"""get methods of user imports as dictionary | ||
|
||
:param user_imports: list of names of user imports | ||
:return: dictionary which contains names of methods of imports as keys and its object as values | ||
""" | ||
output_dict = dict() | ||
for element in user_imports: | ||
output_dict.update(_get_unit_attrs(element)) | ||
return output_dict | ||
|
||
|
||
def _get_unit_attrs(unit: str) -> dict: | ||
"""get attributes of unit by it name | ||
|
||
:param unit: name of unit as string | ||
:return: dictionary which contains name of attribute as key and it object as value | ||
""" | ||
attrs = tuple([i for i in dir(globals()[unit])]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В данном случае сначала создается список, а потом приводится к кортежу :) Созание списка в этом случае абсолютно бесполезно. attrs = tuple(i for i in dir(globals()[unit])) И снова, советую совсем избавиться от |
||
output_dict = dict() | ||
for attribute in attrs: | ||
if not attribute.count('_'): | ||
output_dict[attribute] = getattr(globals()[unit], attribute) | ||
output_dict['abs'] = abs | ||
output_dict['round'] = round | ||
return output_dict | ||
|
||
|
||
def str_parse(ex_str: str, methods: dict) -> list: | ||
"""parse mathematical expression string using regular expressions | ||
|
||
:param ex_str: expression string | ||
:return: list made of parsed expression string | ||
""" | ||
if not ex_str: | ||
raise Exception('brackets are not balanced') | ||
if ex_str.count('(') != ex_str.count(')'): | ||
raise Exception('brackets are not balanced') | ||
|
||
while re.compile(r'[+][-]|[-][+]').findall(ex_str): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Регулярные выражения лучше выносить с отдельные переменные с говорящим названием. |
||
for operators in re.compile(r'[+][-]|[-][+]').findall(ex_str): | ||
ex_str = ex_str.replace(operators, '-') | ||
if not ex_str.count(operators): | ||
break | ||
|
||
while re.compile(r'[\-]{2,}').findall(ex_str) or re.compile(r'[+]{2,}').findall(ex_str): | ||
for minuses in re.compile(r'[\-]{2,}').findall(ex_str): | ||
if minuses.count('-') % 2 == 0: | ||
ex_str = ex_str.replace(minuses, '+') | ||
elif minuses.count('-') % 2 != 0: | ||
ex_str = ex_str.replace(minuses, '-') | ||
for pluses in re.compile(r'[+]{2,}').findall(ex_str): | ||
ex_str = ex_str.replace(pluses, '+') | ||
|
||
for negative_value in constants.RE_NEGATIVE_VALUES.findall(ex_str): | ||
if re.compile(r'[a-zA-Z]+').findall(negative_value) and callable(methods[re.compile(r'[a-zA-Z]+').findall(negative_value)[0]]): | ||
continue | ||
ex_str = ex_str.replace(negative_value, negative_value[0]+'(0'+negative_value[1:]+')') | ||
|
||
while constants.RE_NEGATIVE_FUNCS.findall(ex_str): | ||
for negative_func in constants.RE_NEGATIVE_FUNCS.findall(ex_str): | ||
ex_str = ex_str.replace(negative_func, negative_func[0]+'(0'+negative_func[1:]+')') | ||
|
||
if constants.RE_NEGATIVE_VALUES_ON_STR_BEG.findall(ex_str): | ||
ex_str = '0'+ex_str | ||
|
||
parse_list = constants.RE_MAIN_PARSE_ARG.findall(ex_str) | ||
|
||
packed_expression = parse_funcs_params(parse_list, methods) | ||
|
||
return packed_expression | ||
|
||
|
||
def _priority(expression: str) -> int: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"""evaluate priority of given mathematical operator | ||
|
||
:param expression: mathematical operator | ||
:return: priority of given operator as integer | ||
""" | ||
if expression == constants.lowest_priority_operator: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Как вариант, хранение обозначения оператора и его приоритет можно связать в какую-то общую структуру. |
||
return constants.LOWEST_PRIORITY | ||
elif expression in constants.low_priority_operators: | ||
return constants.LOW_PRIORITY | ||
elif expression in constants.mid_priority_operators: | ||
return constants.MID_PRIORITY | ||
elif expression in constants.high_priority_operators: | ||
return constants.HIGH_PRIORITY | ||
elif expression == constants.highest_priority_operator: | ||
return constants.HIGHEST_PRIORITY | ||
else: | ||
return -1 | ||
|
||
|
||
def polish_notation(expression_list: list, methods: dict) -> list: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Название функции/метода должно обозначать действие There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я насчитал в этой функции 14 операторов условий, что на самом деле очень много. Если есть возможность "без боли" разбить эту функцию на несколько маленьких, то это бы значительно упростило понимание кода. |
||
"""rewrite mathematical expression list into polish notation | ||
|
||
:param expression_list: parsed list of mathematical expression | ||
:param methods: dictionary which contains name of attribute as key and it object as value | ||
:return: rewrited into polish notation list of mathematical expression | ||
""" | ||
output_expression = [] | ||
operation_list = [] | ||
|
||
for expression in expression_list: | ||
if isinstance(expression, list): | ||
output_expression.append(polish_notation(expression, methods)) | ||
continue | ||
if constants.RE_FUNCTIONS.findall(expression) and expression not in methods.keys(): | ||
raise Exception('unknown function {}'.format(expression)) | ||
if constants.RE_FUNCTIONS.findall(expression) and not callable(methods[expression]): | ||
# if we found constant value | ||
output_expression.append(expression) | ||
continue | ||
|
||
elif constants.RE_FUNCTIONS.findall(expression) and callable(methods[expression]): | ||
# if we found mathematical operation | ||
operation_list.append(expression) | ||
|
||
if constants.RE_FLOATS.findall(expression) or constants.RE_INTS.findall(expression) \ | ||
and not constants.RE_FUNCTIONS.findall(expression) : | ||
# if we found number | ||
output_expression.append(expression) | ||
continue | ||
|
||
if constants.RE_OPERATIONS.findall(expression)or(constants.RE_FUNCTIONS.findall(expression) | ||
and not callable(methods[expression])): | ||
if (not operation_list or _priority(expression) > _priority(operation_list[-1])) and expression != '(': | ||
# if we found operator or function and it priority higher than priority of operator on the top of | ||
# operators stack add it into operators stack | ||
operation_list.append(expression) | ||
|
||
elif operation_list and _priority(operation_list[-1]) >= _priority(expression): | ||
# if we found operator and it priority lower than priority of operator on the top of | ||
# operators stack pop all of operators in operators stack into output stack | ||
while operation_list and _priority(operation_list[-1]) >= _priority(expression): | ||
if expression == '(': | ||
break | ||
output_expression.append(operation_list.pop()) | ||
|
||
if expression != '(': | ||
operation_list.append(expression) | ||
|
||
if expression == '(': | ||
operation_list.append(expression) | ||
|
||
if expression == ')': | ||
while operation_list[-1] != '(': | ||
if operation_list: | ||
output_expression.append(operation_list.pop()) | ||
operation_list.pop() | ||
if operation_list and constants.RE_FUNCTIONS.findall(operation_list[-1]): | ||
# if after '(' we found operation also push it into output list | ||
output_expression.append(operation_list.pop()) | ||
|
||
while operation_list: | ||
output_expression.append(operation_list.pop()) | ||
|
||
return output_expression | ||
|
||
|
||
def ex_calc(polish_list: list, methods: dict) -> float: | ||
"""calculate mathematical expression writed as polish notation | ||
|
||
:param polish_list: list which contains mathematical expression writed as polish notation | ||
:param methods: dictionary which contains manes of methods of user imports as keys and objects of it as values | ||
:return: result of calculated mathematical expression | ||
""" | ||
output_list = [] | ||
for ex in polish_list: | ||
if constants.RE_FLOATS.findall(ex) or constants.RE_INTS.findall(ex) and not constants.RE_FUNCTIONS.findall(ex): | ||
output_list.append(ex) | ||
continue | ||
|
||
if constants.RE_FUNCTIONS.findall(ex): | ||
values = [] | ||
|
||
if not callable(methods[ex]): # if word is constant value just write it into output list | ||
output_list.append(methods[ex]) | ||
continue | ||
|
||
signature = inspect.signature(methods[ex]) | ||
parameters_count = len(signature.parameters) | ||
parameters = signature.parameters | ||
for parameter in parameters: # get count of parameters of function | ||
if not parameters[parameter].default: | ||
parameters_count -= 1 | ||
while parameters_count > 0: | ||
if output_list: | ||
float_val = float(output_list.pop()) | ||
values.append(float_val) | ||
parameters_count -= 1 | ||
|
||
# perform mathematical function and write result into output List | ||
output_list.append(methods[ex](*values[::-1])) | ||
|
||
if constants.RE_OPERATIONS.findall(ex): # if found operation perform it | ||
second_val = _get_item_by_type(output_list.pop(), methods) | ||
first_val = _get_item_by_type(output_list.pop(), methods) | ||
output_list.append(constants.operator[ex](first_val, second_val)) | ||
|
||
return _get_item_by_type(output_list[0], methods) | ||
|
||
# goals for weekend | ||
|
||
# argparse (!!!) | ||
# setuptools(!!!) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import argparse | ||
import mainclass | ||
import constants | ||
from math import * | ||
|
||
argparse.ArgumentParser() | ||
|
||
pycalc = mainclass.calculator() | ||
|
||
print(pycalc.calculate(str(input()))) | ||
print(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)))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Для чего тут пустые комментарии?