Skip to content

Commit

Permalink
Improved unary minus
Browse files Browse the repository at this point in the history
  • Loading branch information
charon25 committed Apr 21, 2023
1 parent 3ca506d commit f6bf043
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 27 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"
)
14 changes: 7 additions & 7 deletions shunting_yard/shunting_yard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())

Expand Down
3 changes: 2 additions & 1 deletion shunting_yard/tokenize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 11 additions & 5 deletions tests/test_shunting_yard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
24 changes: 12 additions & 12 deletions tests/test_tokenize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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', ')'])
Expand All @@ -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', ')'])
Expand Down

0 comments on commit f6bf043

Please sign in to comment.