Skip to content

Commit 3ca506d

Browse files
committed
Added possibility to replace scientific notation
1 parent d22b540 commit 3ca506d

File tree

5 files changed

+61
-10
lines changed

5 files changed

+61
-10
lines changed

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="shunting-yard",
8-
version="1.0.9",
8+
version="1.0.10",
99
author="Paul 'charon25' Kern",
1010
description="Compute any math expression",
1111
long_description=long_description,
@@ -14,5 +14,5 @@
1414
url="https://www.github.com/charon25/ShuntingYard",
1515
license="MIT",
1616
packages=['shunting_yard'],
17-
download_url="https://github.com/charon25/ShuntingYard/archive/refs/tags/v1.0.9.tar.gz"
17+
download_url="https://github.com/charon25/ShuntingYard/archive/refs/tags/v1.0.10.tar.gz"
1818
)

shunting_yard/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@
1111

1212
SEPARATORS_NO_CLOSING_BRACKET = '(,;'
1313
SEPARATORS = SEPARATORS_NO_CLOSING_BRACKET + ')'
14+
15+
16+
IMPLICIT_MULTIPLICATION_NUMBER_REGEX = r'\b(\d+)([^)\d.,;+*\/^-])'
17+
IMPLICIT_MULTIPLICATION_BRACKET_REGEX = r'(\))([^),;+*\/^-])'
18+
19+
20+
SCIENTIFIC_NOTATION_AFTER_DOT_REGEX = r'([+-]?\d*\.?\d+)e([+-]?\d+)'
21+
SCIENTIFIC_NOTATION_BEFORE_DOT_REGEX = r'([+-]?\d+\.?\d*)e([+-]?\d+)'

shunting_yard/shunting_yard.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Associativity(Enum):
4343

4444

4545
# Reference : https://en.wikipedia.org/wiki/Shunting_yard_algorithm
46-
def shunting_yard(expression: str, case_sensitive: bool = True, variable: Optional[str] = None) -> str:
46+
def shunting_yard(expression: str, case_sensitive: bool = True, variable: Optional[str] = None, convert_scientific_notation: bool = True) -> str:
4747
"""Convert the given classical math expression into Reverse Polish Notation using the Shunting-yard algorithm (see https://en.wikipedia.org/wiki/Shunting_yard_algorithm for more details). All whitespace are ignored.
4848
4949
@@ -56,8 +56,9 @@ def shunting_yard(expression: str, case_sensitive: bool = True, variable: Option
5656
5757
Args:
5858
expression (str): string containing the mathematical expression to convert.
59-
case_sensitive (bool): indicates whether the expression should care about case.
60-
variable (str, optional): if defined, will treat every token matching the variable as a number.
59+
case_sensitive (bool): indicates whether the expression should care about case (default: True).
60+
variable (str, optional): if defined, will treat every token matching the variable as a number (default: None).
61+
convert_scientific_notation (bool, optional): indicates whether the expression should convert scientific notation (e.g. 1.23e4 to 1.23*10^4) (default: True).
6162
6263
Raises:
6364
MismatchedBracketsError: raised if the bracket are unbalanced.
@@ -72,7 +73,7 @@ def shunting_yard(expression: str, case_sensitive: bool = True, variable: Option
7273
if not case_sensitive:
7374
expression = expression.lower()
7475

75-
for token in tokenize(expression):
76+
for token in tokenize(expression, convert_scientific_notation=convert_scientific_notation):
7677
first_char = token[0]
7778

7879
if first_char in NUMBER_CHARS or token == variable:

shunting_yard/tokenize.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Iterator
33

44
from shunting_yard.constants import BASE_OPERATORS, FUNCTION_CHARS, FUNCTION_FIRST_CHARS, NUMBER_CHARS, SEPARATORS, UNARY_OPERATORS
5+
from shunting_yard.constants import IMPLICIT_MULTIPLICATION_BRACKET_REGEX, IMPLICIT_MULTIPLICATION_NUMBER_REGEX, SCIENTIFIC_NOTATION_AFTER_DOT_REGEX, SCIENTIFIC_NOTATION_BEFORE_DOT_REGEX
56

67

78

@@ -18,18 +19,34 @@ def _remove_implicit_multiplication(expression: str) -> str:
1819
"""
1920

2021
# Insert '*' between a number and anything other than a digit, an operation, a closing bracket, a decimal dot, a function parameters separator
21-
expression = re.sub(r'\b(\d+)([^)\d.,;+*\/^-])', r'\1*\2', expression)
22+
expression = re.sub(IMPLICIT_MULTIPLICATION_NUMBER_REGEX, r'\1*\2', expression)
2223
# Insert '*' between a closing bracket and anything other than an operation, another closing bracket, a function parameters separator
23-
expression = re.sub(r'(\))([^),;+*\/^-])', r'\1*\2', expression)
24+
expression = re.sub(IMPLICIT_MULTIPLICATION_BRACKET_REGEX, r'\1*\2', expression)
2425
return expression
2526

2627

27-
def tokenize(string: str) -> Iterator[str]:
28+
def _convert_scientific_notation(expression: str) -> str:
29+
""""""
30+
31+
# Replace everything of the form "xey" by "x*10^y" where y is an integer and x is a float not ending with just a dot
32+
expression = re.sub(SCIENTIFIC_NOTATION_AFTER_DOT_REGEX, r'\1*10^(\2)', expression)
33+
34+
# Replace everything of the form "xey" by "x*10^y" where y is an integer and x is a float not starting with just a dot
35+
expression = re.sub(SCIENTIFIC_NOTATION_BEFORE_DOT_REGEX, r'\1*10^(\2)', expression)
36+
37+
return expression
38+
39+
40+
def tokenize(string: str, convert_scientific_notation: bool = True) -> Iterator[str]:
2841
if string == '':
2942
return
3043

3144
# Remove all whitespaces are they do not change anything
3245
string = ''.join(string.split())
46+
47+
if convert_scientific_notation:
48+
string = _convert_scientific_notation(string)
49+
3350
string = _remove_implicit_multiplication(string)
3451

3552
cursor = 0

tests/test_tokenize.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import unittest
22

33
from shunting_yard import tokenize
4-
from shunting_yard.tokenize import _remove_implicit_multiplication
4+
from shunting_yard.tokenize import _convert_scientific_notation, _remove_implicit_multiplication
55

66

77
class TestTokenizer(unittest.TestCase):
@@ -106,6 +106,7 @@ def test_digit_other(self):
106106
self.assertEqual(_remove_implicit_multiplication('3cos(5)'), '3*cos(5)')
107107
self.assertEqual(_remove_implicit_multiplication('3_func(0)'), '3*_func(0)')
108108
self.assertEqual(_remove_implicit_multiplication('1(2+3)'), '1*(2+3)')
109+
self.assertEqual(_remove_implicit_multiplication('1(2(3(4(5(6(7(8(9(10+0)))))))))'), '1*(2*(3*(4*(5*(6*(7*(8*(9*(10+0)))))))))')
109110

110111
def test_implicit_mult_double_brackets(self):
111112
self.assertEqual(_remove_implicit_multiplication('(1+2)(3+4)'), '(1+2)*(3+4)')
@@ -127,6 +128,30 @@ def test_with_longer_numbers(self):
127128
self.assertEqual(_remove_implicit_multiplication('1+200x'), '1+200*x')
128129

129130

131+
class TestScientificNotation(unittest.TestCase):
132+
133+
def test_not_present(self):
134+
self.assertEqual(_convert_scientific_notation('abc'), 'abc')
135+
self.assertEqual(_convert_scientific_notation('123.4'), '123.4')
136+
self.assertEqual(_convert_scientific_notation('123exp(4)'), '123exp(4)')
137+
self.assertEqual(_convert_scientific_notation('123e +1'), '123e +1')
138+
139+
def test_present(self):
140+
self.assertEqual(_convert_scientific_notation('12e3'), '12*10^(3)')
141+
self.assertEqual(_convert_scientific_notation('+12e3'), '+12*10^(3)')
142+
self.assertEqual(_convert_scientific_notation('-12e3'), '-12*10^(3)')
143+
self.assertEqual(_convert_scientific_notation('1.2e3'), '1.2*10^(3)')
144+
self.assertEqual(_convert_scientific_notation('1.e3'), '1.*10^(3)')
145+
self.assertEqual(_convert_scientific_notation('.2e3'), '.2*10^(3)')
146+
self.assertEqual(_convert_scientific_notation('12e-3'), '12*10^(-3)')
147+
self.assertEqual(_convert_scientific_notation('12e+3'), '12*10^(+3)')
148+
self.assertEqual(_convert_scientific_notation('12e34'), '12*10^(34)')
149+
self.assertEqual(_convert_scientific_notation('12e-34'), '12*10^(-34)')
150+
self.assertEqual(_convert_scientific_notation('12e+34'), '12*10^(+34)')
151+
152+
def test_double(self):
153+
self.assertEqual(_convert_scientific_notation('12e3+45e-6'), '12*10^(3)+45*10^(-6)')
154+
130155

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

0 commit comments

Comments
 (0)