From f6bf04383b82258d817c1d8a04568738af3b8b5e Mon Sep 17 00:00:00 2001 From: Paul Kern Date: Fri, 21 Apr 2023 21:11:42 +0200 Subject: [PATCH] Improved unary minus --- README.md | 4 ++++ setup.py | 4 ++-- shunting_yard/shunting_yard.py | 14 +++++++------- shunting_yard/tokenize.py | 3 ++- tests/test_shunting_yard.py | 16 +++++++++++----- tests/test_tokenize.py | 24 ++++++++++++------------ 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index e5e7449..45a37cd 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,7 @@ print(list(sy.tokenize('1+(2 * 3) - 4 * (2 / 3)'))) # ['1', '+', '(', '2', '*', '3', ')', '-', '4', '*', '(', '2', '/', '3', ')'] ``` + +## Note + +Unary minus (e.g. `-1` or `-(x+1)`) will be converted to `(0-1)*` before computing the RPN of the expression. Unary plus will be removed. diff --git a/setup.py b/setup.py index 92e2978..1db36bd 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="shunting-yard", - version="1.0.10", + version="1.0.11", author="Paul 'charon25' Kern", description="Compute any math expression", long_description=long_description, @@ -14,5 +14,5 @@ url="https://www.github.com/charon25/ShuntingYard", license="MIT", packages=['shunting_yard'], - download_url="https://github.com/charon25/ShuntingYard/archive/refs/tags/v1.0.10.tar.gz" + download_url="https://github.com/charon25/ShuntingYard/archive/refs/tags/v1.0.11.tar.gz" ) diff --git a/shunting_yard/shunting_yard.py b/shunting_yard/shunting_yard.py index ed2c73c..40fea31 100644 --- a/shunting_yard/shunting_yard.py +++ b/shunting_yard/shunting_yard.py @@ -17,8 +17,6 @@ class MismatchedBracketsError(Exception): '*': 20, '/': 20, '^': 30, - '-u': 40, - '+u': 40, } # Returns the precendence if the operator is one of +-*/^, or infinity if it is a function @@ -105,11 +103,13 @@ def shunting_yard(expression: str, case_sensitive: bool = True, variable: Option output.append(operator_stack.pop()) elif first_char in BASE_OPERATORS: - while (len(operator_stack) > 0 and - (operator := operator_stack[-1]) not in SEPARATORS_NO_CLOSING_BRACKET and - (get_precedence(operator) > get_precedence(token) or - (get_precedence(operator) == get_precedence(token) and - OPERATORS_ASSOCIATIVITY[token] == Associativity.LEFT))): + while (len(operator_stack) > 0 and + (prev_operator := operator_stack[-1]) not in SEPARATORS_NO_CLOSING_BRACKET and ( + get_precedence(prev_operator) > get_precedence(token) or ( + get_precedence(prev_operator) == get_precedence(token) and + OPERATORS_ASSOCIATIVITY[token] == Associativity.LEFT + ) + )): output.append(operator_stack.pop()) diff --git a/shunting_yard/tokenize.py b/shunting_yard/tokenize.py index 315c8c4..2d169cb 100644 --- a/shunting_yard/tokenize.py +++ b/shunting_yard/tokenize.py @@ -56,7 +56,8 @@ def tokenize(string: str, convert_scientific_notation: bool = True) -> Iterator[ char = string[cursor] if not is_infix and char in UNARY_OPERATORS: - yield f'{char}u' + if char == '-': + yield from '(0-1)*' cursor += 1 elif char in BASE_OPERATORS or char in SEPARATORS: diff --git a/tests/test_shunting_yard.py b/tests/test_shunting_yard.py index 6a36474..c70c3fc 100644 --- a/tests/test_shunting_yard.py +++ b/tests/test_shunting_yard.py @@ -48,11 +48,17 @@ def test_case_sensitive(self): self.assertEqual(shunting_yard('min(1, 2)', case_sensitive=False), shunting_yard('MIN(1, 2)', case_sensitive=False)) def test_unary_in_function(self): - self.assertEqual(shunting_yard('min(-1, 2)'), '1 -u 2 min') - self.assertEqual(shunting_yard('min(-(1+3), 2)'), '1 3 + -u 2 min') - self.assertEqual(shunting_yard('min(1, -2)'), '1 2 -u min') - self.assertEqual(shunting_yard('min(1, -(2+3))'), '1 2 3 + -u min') - self.assertEqual(shunting_yard('min(1, -min(3, 4))'), '1 3 4 min -u min') + self.assertEqual(shunting_yard('min(-1, 2)'), '0 1 - 1 * 2 min') + self.assertEqual(shunting_yard('min(-(1+3), 2)'), '0 1 - 1 3 + * 2 min') + self.assertEqual(shunting_yard('min(1, -2)'), '1 0 1 - 2 * min') + self.assertEqual(shunting_yard('min(1, -(2+3))'), '1 0 1 - 2 3 + * min') + self.assertEqual(shunting_yard('min(1, -min(3, 4))'), '1 0 1 - 3 4 min * min') + + self.assertEqual(shunting_yard('min(+1, 2)'), '1 2 min') + self.assertEqual(shunting_yard('min(+(1+3), 2)'), '1 3 + 2 min') + self.assertEqual(shunting_yard('min(1, +2)'), '1 2 min') + self.assertEqual(shunting_yard('min(1, +(2+3))'), '1 2 3 + min') + self.assertEqual(shunting_yard('min(1, +min(3, 4))'), '1 3 4 min min') def test_variable(self): self.assertEqual(shunting_yard('min(x, 1)'), 'x 1 min') diff --git a/tests/test_tokenize.py b/tests/test_tokenize.py index 062bf40..bf6da68 100644 --- a/tests/test_tokenize.py +++ b/tests/test_tokenize.py @@ -38,7 +38,7 @@ def test_function_one_argument(self): self.assertListEqual(list(tokenize('sin(1 + 4)')), ['sin', '(', '1', '+', '4', ')']) def test_function_no_argument(self): - self.assertListEqual(list(tokenize('-3/2*pi')), ['-u', '3', '/', '2', '*', 'pi']) + self.assertListEqual(list(tokenize('-3/2*pi')), ['(', '0', '-', '1', ')', '*', '3', '/', '2', '*', 'pi']) def test_function_2_arguments(self): self.assertListEqual(list(tokenize('max(1, 4)')), ['max', '(', '1', ',', '4', ')']) @@ -54,21 +54,21 @@ def test_function_underscore(self): self.assertListEqual(list(tokenize('arc_cos(0)')), ['arc_cos', '(', '0', ')']) def test_function_unary_minus(self): - self.assertListEqual(list(tokenize('-1')), ['-u', '1']) - self.assertListEqual(list(tokenize('-(1+2)')), ['-u', '(', '1', '+', '2', ')']) - self.assertListEqual(list(tokenize('2*-1')), ['2', '*', '-u', '1']) - self.assertListEqual(list(tokenize('-(-1)')), ['-u', '(', '-u', '1', ')']) + self.assertListEqual(list(tokenize('-1')), ['(', '0', '-', '1', ')', '*', '1']) + self.assertListEqual(list(tokenize('-(1+2)')), ['(', '0', '-', '1', ')', '*', '(', '1', '+', '2', ')']) + self.assertListEqual(list(tokenize('2*-1')), ['2', '*', '(', '0', '-', '1', ')', '*', '1']) + self.assertListEqual(list(tokenize('-(-1)')), ['(', '0', '-', '1', ')', '*', '(', '(', '0', '-', '1', ')', '*', '1', ')']) def test_function_unary_plus(self): - self.assertListEqual(list(tokenize('+1')), ['+u', '1']) - self.assertListEqual(list(tokenize('+(1+2)')), ['+u', '(', '1', '+', '2', ')']) - self.assertListEqual(list(tokenize('2*+1')), ['2', '*', '+u', '1']) - self.assertListEqual(list(tokenize('+(+1)')), ['+u', '(', '+u', '1', ')']) + self.assertListEqual(list(tokenize('+1')), ['1']) + self.assertListEqual(list(tokenize('+(1+2)')), ['(', '1', '+', '2', ')']) + self.assertListEqual(list(tokenize('2*+1')), ['2', '*', '1']) + self.assertListEqual(list(tokenize('+(+1)')), ['(', '1', ')']) def test_function_unary_in_function(self): - self.assertListEqual(list(tokenize('min(-1, 2)')), ['min', '(', '-u', '1', ',', '2', ')']) - self.assertListEqual(list(tokenize('min(1, -2)')), ['min', '(', '1', ',', '-u', '2', ')']) - self.assertListEqual(list(tokenize('min(1, -(2+sin(3)))')), ['min', '(', '1', ',', '-u', '(', '2', '+', 'sin', '(', '3', ')', ')', ')']) + self.assertListEqual(list(tokenize('min(-1, 2)')), ['min', '(', '(', '0', '-', '1', ')', '*', '1', ',', '2', ')']) + self.assertListEqual(list(tokenize('min(1, -2)')), ['min', '(', '1', ',', '(', '0', '-', '1', ')', '*', '2', ')']) + self.assertListEqual(list(tokenize('min(1, -(2+sin(3)))')), ['min', '(', '1', ',', '(', '0', '-', '1', ')', '*', '(', '2', '+', 'sin', '(', '3', ')', ')', ')']) def test_digits_in_function_name(self): self.assertListEqual(list(tokenize('min3(1, 2)')), ['min3', '(', '1', ',', '2', ')'])