티스토리 뷰

[조건]


- 계산기에 들어갈 수식을 생성한다.


인자값

ops : 연산자의 갯수

max: 숫자의 최대값

bk: 연산자 하나당 괄호의 생성 확률

types: 사용할 연산자 타입들


리턴값

수식 문자열



예시

create(ops=10, max=10000, bk=30, types="+-*/")

연산자의 갯수는 10개
숫자는 모든 숫자는 0~10000 까지 중에 랜덤한 값

연산자 당 괄호가 생성될 확률은 30%

사용할 연산자들은 +-*/



























generator.py

from random import randrange, choice


def _test(ops: int, max_: int, bk: int, types: str):
"""
>>> _test(ops=100, max_=10000, bk=30, types="+-*/")
True
"""
result = create(ops=ops, max_=max_, bk=bk, types=types)
count_ops = sum([result.count(x) for x in types])
in_range_num = max([int(x) for x in result if x.isdigit()])
is_types = not bool([x for x in result if x in "+-*/" and x not in types])

if count_ops == ops and in_range_num < max_ and is_types:
return True
return False


def _unit(max_: int, bk: int, types: str)->list:
s = [str(randrange(1, max_)), choice(types), str(randrange(1, max_))]
return ["(", *s, ")"] if randrange(100) < bk else s


def _push(i: int, r: list, max_: int, bk: int, types: str)->list:
return r[:i] + _unit(max_, bk, types) + r[i + 1:]


def create(ops: int, max_: int, bk: int, types: str)->str:
r = ["0"]
for _ in range(ops):
i = choice([i for i, c in enumerate(r) if c.isdigit()])
r = _push(i, r, max_, bk, types)
return "".join(r)


if __name__ == '__main__':
import doctest
doctest.testmod()


main.py

from functools import wraps
import generator
import unittest


def _handler(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except AssertionError as e:
return e
except ZeroDivisionError as e:
return e
except SyntaxError as e:
return e
return wrapper


def _operator(ns: list)->str:
ns[0], ns[2] = float(ns[0]), float(ns[2])
func = {"+": lambda: ns[0] + ns[2],
"-": lambda: ns[0] - ns[2],
"*": lambda: ns[0] * ns[2],
"/": lambda: ns[0] / ns[2]}[ns[1]]
return str(func())


def _compile(os: str, r: list, i=0)->list:
while os[0] in r or os[1] in r:
if r[i] in os:
r = [*(r[:i-1]), _operator(r[i - 1:i + 2]), *(r[i + 2:])]
i -= 1
i += 1
return r


def _compile_all(r: list, tks=("*/", "+-"))->str:
for tk in tks:
r = _compile(tk, r)
return r[0]


def _progress(r: list, i=0)->str:
st = []
while "(" in r and ")" in r:
if r[i] == "(":
st += [i+1]
elif r[i] == ")":
if len(st):
j = st.pop()
t = r[j:i]
r = r[:j-1] + [_compile_all(t)] + r[i + 1:]
i -= len(t) + 2
i += 1
return _compile_all(r)


def _collect_digit(s: str, bf="")->list:
r = []
for c in s:
if c.isdigit() or c is '.':
bf += c
else:
r += [bf, c] if bf else [c]
bf = ""
r += [bf] if bf else []
return r


def _filter(s: str, tks=("",))->str:
for tk in tks:
s = s.replace(tk, "")
return s


def _is_syntax(s: str, patterns_syntax=("[\\d][\\s]+[\\d]", "[-+*/][\\s]*[-+*/]"))->bool:
import re
for t in patterns_syntax:
p = re.compile(t)
if bool(p.findall(s)):
return False
return True


def _is_bracket(s: str)->bool:
st = []
for c in s:
if c == "(":
st += [c]
elif c == ")":
if bool(st):
st.pop()
else:
return False
return not bool(st)


def _is_valid(s: str)->None:
if not _is_syntax(s) or not _is_bracket(s):
raise SyntaxError("Invalid syntax")


@_handler
def calculator(s: str)->float:
"""
calculator from equation
:param s: equation string
:return: answer is float
"""
_is_valid(s)
result = _filter(s)
result = _collect_digit(result)
result = _progress(result)
return float(result)


@_handler
def eval_(s):
return float(eval(s))


class Test(unittest.TestCase):
threshold_rate = 0.00000000001

def _chk_rate(self, a, b):
error_rate = abs(a - b) / a
self.assertTrue(error_rate < Test.threshold_rate)

def test_operator(self):
inputs = ( ["1", "+", "2"], ["2", "-", "3.55"], ["5", "*", "2.1"], ["123.123", "/", "234"])
answers = ('3.0', "-1.55", '10.5', '0.5261666666666667')
for input_, answer in zip(inputs, answers):
self._chk_rate(float(_operator(input_)), float(answer))

def test_collect_digit(self):
input_ = ("((1+2)-(3+4)/5555+2-3.4123123+5.6234324)",)
answer = ('1', '2', '3', '4', '5555', '2', '3.4', '5.6')
numbers = [c for c in _collect_digit(input_[0]) if c.isdigit()]
for o, a in zip(numbers, answer):
self.assertEqual(o, a)

def test_is_bracket(self):
wrong_cases = ("())", "((())", "))((", ")))()()", "(()()(())")
right_cases = ("((()()))", "()()()", "((()))((()))")
for right, wrong in zip(right_cases, wrong_cases):
self.assertTrue(_is_bracket(right))
self.assertFalse(_is_bracket(wrong))

def test_calculator(self):
for _ in range(100):
cmd = generator.create(ops=100, max_=10000, bk=30, types="+-*/")
a = calculator(cmd)
b = eval_(cmd)
self._chk_rate(a, b)


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


댓글