Skip to content

Commit

Permalink
Split out example parsers into separate modules in the examples direc…
Browse files Browse the repository at this point in the history
…tory.
  • Loading branch information
ptmcg committed May 5, 2021
1 parent 343815f commit a84eec5
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 203 deletions.
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ plusminus Change Log
using (), (], [) or [] notation ("in" with sets is still
supported).

- Deleted the example_parsers.py module in the examples directory, and split
the parsers out into separate modules in that directory.

- Added __version_info__ structure, following pattern of sys.version_info
field names.

Expand Down
63 changes: 63 additions & 0 deletions plusminus/examples/business_arithmetic_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#
# business_arithmetic_parser.py
#
# Copyright 2021, Paul McGuire
#
from plusminus import BaseArithmeticParser, safe_pow


class BusinessArithmeticParser(BaseArithmeticParser):
"""
A parser for evaluating common financial and retail calculations:
50% of 20
20 * (1-20%)
(100-20)% of 20
5 / 20%
FV(20000, 3%, 30)
FV(20000, 3%/12, 30*12)
Functions:
FV(present_value, rate_per_period, number_of_periods)
future value of an amount, n periods in the future, at an interest rate per period
PV(future_value, rate_per_period, number_of_periods)
present value of a future amount, n periods in the future, at an interest rate per period
PP(present_value, rate_per_period, number_of_periods)
periodic value of n amounts, one per period, for n periods, at an interest rate per period
"""

def customize(self):
def pv(future_value, rate, n_periods):
return future_value / safe_pow(1 + rate, n_periods)

def fv(present_value, rate, n_periods):
return present_value * safe_pow(1 + rate, n_periods)

def pp(present_value, rate, n_periods):
return rate * present_value / (1 - safe_pow(1 + rate, -n_periods))

super().customize()
self.add_operator("of", 2, BaseArithmeticParser.LEFT, lambda a, b: a * b)
self.add_operator("%", 1, BaseArithmeticParser.LEFT, lambda a: a / 100)

self.add_function("PV", 3, pv)
self.add_function("FV", 3, fv)
self.add_function("PP", 3, pp)


if __name__ == '__main__':

parser = BusinessArithmeticParser()
parser.runTests(
"""\
25%
20 * 50%
50% of 20
20 * (1-20%)
(100-20)% of 20
5 / 20%
FV(20000, 3%, 30)
FV(20000, 3%/12, 30*12)
""",
postParse=lambda _, result: result[0].evaluate(),
)
50 changes: 50 additions & 0 deletions plusminus/examples/combinatorics_arithmetic_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#
# combinatorics_arithmetic_parser.py
#
# Copyright 2021, Paul McGuire
#
from plusminus import BaseArithmeticParser, ArithmeticParser, constrained_factorial


class CombinatoricsArithmeticParser(BaseArithmeticParser):
"""
Parser for evaluating expressions of combinatorics problems, for numbers of
permutations (nPm) and combinations (nCm):
nPm = n! / (n-m)!
8P4 = number of (ordered) permutations of selecting 4 items from a collection of 8
nCm = n! / m!(n-m)!
8C4 = number of (unordered) combinations of selecting 4 items from a collection of 8
"""

def customize(self):
super().customize()
# fmt: off
self.add_operator("P", 2, BaseArithmeticParser.LEFT,
lambda a, b: int(constrained_factorial(a) / constrained_factorial(a - b)))
self.add_operator("C", 2, BaseArithmeticParser.LEFT,
lambda a, b: int(constrained_factorial(a)
/ constrained_factorial(b)
/ constrained_factorial(a - b)))
self.add_operator(*ArithmeticParser.Operators.FACTORIAL)
# fmt: on


if __name__ == '__main__':

parser = CombinatoricsArithmeticParser()
parser.runTests(
"""\
3!
-3!
3!!
6! / (6-2)!
6 P 2
6! / (2!*(6-2)!)
6 C 2
6P6
6C6
""",
postParse=lambda _, result: result[0].evaluate(),
)
59 changes: 59 additions & 0 deletions plusminus/examples/date_time_arithmetic_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#
# date_time_arithmetic_parser.py
#
# Copyright 2021, Paul McGuire
#
from plusminus import BaseArithmeticParser


class DateTimeArithmeticParser(BaseArithmeticParser):
"""
Parser for evaluating expressions in dates and times, using operators d, h, m, and s
to define terms for amounts of days, hours, minutes, and seconds:
now()
today()
now() + 10s
now() + 24h
All numeric expressions will be treated as UTC integer timestamps. To display
timestamps as ISO strings, use str():
str(now())
str(today() + 3d)
"""

SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60
SECONDS_PER_DAY = SECONDS_PER_HOUR * 24

def customize(self):
from datetime import datetime

# fmt: off
self.add_operator("d", 1, BaseArithmeticParser.LEFT, lambda t: t * DateTimeArithmeticParser.SECONDS_PER_DAY)
self.add_operator("h", 1, BaseArithmeticParser.LEFT, lambda t: t * DateTimeArithmeticParser.SECONDS_PER_HOUR)
self.add_operator("m", 1, BaseArithmeticParser.LEFT, lambda t: t * DateTimeArithmeticParser.SECONDS_PER_MINUTE)
self.add_operator("s", 1, BaseArithmeticParser.LEFT, lambda t: t)

self.add_function("now", 0, lambda: datetime.utcnow().timestamp())
self.add_function("today", 0,
lambda: datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0).timestamp())
self.add_function("str", 1, lambda dt: str(datetime.fromtimestamp(dt)))
# fmt: on


if __name__ == "__main__":

parser = DateTimeArithmeticParser()
parser.runTests(
"""\
now()
str(now())
str(today())
"A day from now: " + str(now() + 1d)
"A day and an hour from now: " + str(now() + 1d + 1h)
str(now() + 3*(1d + 1h))
""",
postParse=lambda _, result: result[0].evaluate(),
)
40 changes: 40 additions & 0 deletions plusminus/examples/dice_roll_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# dice_roll_parser.py
#
# Copyright 2021, Paul McGuire
#
from plusminus import BaseArithmeticParser


class DiceRollParser(BaseArithmeticParser):
"""
Parser for evaluating expressions representing rolls of dice, as used in many board and
role-playing games, such as:
d20
3d20
5d6 + d20
"""

def customize(self):
import random

# fmt: off
self.add_operator("d", 1, BaseArithmeticParser.RIGHT, lambda a: random.randint(1, a))
self.add_operator("d", 2, BaseArithmeticParser.LEFT,
lambda a, b: sum(random.randint(1, b) for _ in range(a)))
# fmt: on


if __name__ == '__main__':

parser = DiceRollParser()
parser.runTests(
"""\
d20
3d6
d20+3d4
2d100
""",
postParse=lambda _, result: result[0].evaluate(),
)
Loading

0 comments on commit a84eec5

Please sign in to comment.