Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds complex math expressions support #3

Merged
merged 9 commits into from
Jul 17, 2020
177 changes: 126 additions & 51 deletions lambdas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-

import operator
from typing import Callable, Mapping, TypeVar
from functools import partial, reduce
from typing import Callable, List, Mapping, TypeVar, Union

from typing_extensions import Protocol

T1 = TypeVar('T1')
T2 = TypeVar('T2')

_Number = Union[int, float, complex]


def _fmap(callback):
"""Convers callback to instance method with two arguments."""
Expand Down Expand Up @@ -45,7 +48,87 @@ class _LambdaDynamicProtocol(Protocol[T1]):
lambdas_generic_field: T1


class _Callable(object):
class _MathExpression(object): # noqa: WPS214
"""
Mathmatical expression callable class.

This class helps us to build an callable with complex mathematical
expression, basically it's the substitute of `x` in a expression.
When we call this class the number passed trought the instance will be
the `x`.

See the example below:

>>> from lambdas import _MathExpression
>>> complex_expression = (10 ** 2) / _MathExpression() * 10
>>> complex_expression(2)
500.0

"""

def __init__(self) -> None:
self._operations: List[Callable[[_Number], _Number]] = []

def __add__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.add), other)

def __sub__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.sub), other)

def __mul__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.mul), other)

def __floordiv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.floordiv), other)

def __truediv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.truediv), other)

def __mod__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.mod), other)

def __pow__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.pow), other)

def __radd__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.add, other)

def __rsub__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.sub, other)

def __rmul__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.mul, other)

def __rfloordiv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.floordiv, other)

def __rtruediv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.truediv, other)

def __rmod__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.mod, other)

def __rpow__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.pow, other)

def __call__(self, number: _Number) -> _Number:
first_operation, *rest_of_the_operations = self._operations
return reduce(
lambda partial_result, operation: operation(partial_result),
rest_of_the_operations,
first_operation(number),
)

def _add_operation(
self,
operation: Callable[[_Number, _Number], _Number],
other: _Number,
) -> '_MathExpression':
self._operations.append(partial(operation, other))
return self


class _Callable(object): # noqa: WPS214
"""
Short lambda implementation.

Expand All @@ -72,21 +155,47 @@ def __getitem__(
) -> Callable[[Mapping[T1, T2]], T2]:
return operator.itemgetter(key)

__add__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.add,
)
__mul__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.mul,
)
__sub__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.sub,
)
__mod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.mod,
)
__pow__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.pow,
)
def __add__(self, other: _Number) -> _MathExpression:
return _MathExpression() + other

def __sub__(self, other: _Number) -> _MathExpression:
return _MathExpression() - other

def __mul__(self, other: _Number) -> _MathExpression:
return _MathExpression() * other

def __floordiv__(self, other: _Number) -> _MathExpression:
return _MathExpression() // other

def __truediv__(self, other: _Number) -> _MathExpression:
return _MathExpression() / other

def __mod__(self, other: _Number) -> _MathExpression:
return _MathExpression() % other

def __pow__(self, other: _Number) -> _MathExpression:
return _MathExpression() ** other

def __radd__(self, other: _Number) -> _MathExpression:
return other + _MathExpression()

def __rsub__(self, other: _Number) -> _MathExpression:
return other - _MathExpression()

def __rmul__(self, other: _Number) -> _MathExpression:
return other * _MathExpression()

def __rfloordiv__(self, other: _Number) -> _MathExpression:
return other // _MathExpression()

def __rtruediv__(self, other: _Number) -> _MathExpression:
return other / _MathExpression()

def __rmod__(self, other: _Number) -> _MathExpression:
return other % _MathExpression() # noqa: S001

def __rpow__(self, other: _Number) -> _MathExpression:
return other ** _MathExpression()

__and__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.and_,
Expand All @@ -97,17 +206,7 @@ def __getitem__(
__xor__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.xor,
)

__div__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.truediv,
)
__divmod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(divmod)
__floordiv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.floordiv,
)
__truediv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.truediv,
)

__lshift__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.lshift,
Expand Down Expand Up @@ -149,33 +248,9 @@ def __getitem__(
operator.invert,
)

__radd__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.add),
)
__rmul__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.mul),
)
__rsub__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.sub),
)
__rmod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.mod),
)
__rpow__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.pow),
)
__rdiv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.truediv),
)
__rdivmod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(divmod),
)
__rtruediv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.truediv),
)
__rfloordiv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.floordiv),
)

__rlshift__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.lshift),
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ exclude =
*.egg
temp

ignore = D100, D104, D401, W504, X100, WPS121, RST303, RST304
ignore = D100, D104, D401, W504, X100, WPS121, RST303, RST304, WPS204

per-file-ignores =
# Disable imports in `__init__.py`:
Expand Down
8 changes: 8 additions & 0 deletions tests/test_callable/test_complex_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_complex_expression():
"""Ensures that add works correctly."""
assert ((10 ** 5) / (_ % 3) * 9)(5) == 450000.0
13 changes: 13 additions & 0 deletions tests/test_callable/test_floordiv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_floordiv():
"""Ensures that add works correctly."""
assert (_ // 5)(33) == 6


def test_rfloordiv():
"""Ensures that add works correctly."""
assert (_ // 5)(33) == (33 // _)(5)
13 changes: 13 additions & 0 deletions tests/test_callable/test_mod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_mod():
"""Ensures that add works correctly."""
assert (_ % 35)(140) == 0


def test_rmod():
"""Ensures that add works correctly."""
assert (149 % _)(36) == (_ % 36)(149)
13 changes: 13 additions & 0 deletions tests/test_callable/test_mul.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_mul():
"""Ensures that add works correctly."""
assert (_ * 20)(10) == 200


def test_rmul():
"""Ensures that add works correctly."""
assert (99 * _)(4) == (_ * 99)(4)
13 changes: 13 additions & 0 deletions tests/test_callable/test_pow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_pow():
"""Ensures that add works correctly."""
assert (_ ** 3)(2) == 8


def test_rpow():
"""Ensures that add works correctly."""
assert (3 ** _)(4) == (_ ** 4)(3)
13 changes: 13 additions & 0 deletions tests/test_callable/test_sub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_sub():
"""Ensures that add works correctly."""
assert (_ - 10)(1) + 1 == -8


def test_rsub():
"""Ensures that add works correctly."""
assert (10 - _)(1) + (_ - 10)(1) == 0
13 changes: 13 additions & 0 deletions tests/test_callable/test_truediv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_truediv():
"""Ensures that add works correctly."""
assert (_ / 6)(39) == 6.5


def test_rtruediv():
"""Ensures that add works correctly."""
assert (42 / _)(8) == (_ / 8)(42)
16 changes: 16 additions & 0 deletions tests/test_math_expression/test_add_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_add():
"""Ensures that add works correctly."""
add = _MathExpression() + 2
assert add(1) + 1 == 4


def test_radd():
"""Ensures that add works correctly."""
add = _MathExpression() + 2
radd = 2 + _MathExpression()
assert add(1) == radd(1)
9 changes: 9 additions & 0 deletions tests/test_math_expression/test_complex_math_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_complex_expression():
"""Ensures that add works correctly."""
complex_expression = _MathExpression() * 2 + 1
assert complex_expression(1) == 3
16 changes: 16 additions & 0 deletions tests/test_math_expression/test_floordiv_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_floordiv():
"""Ensures that add works correctly."""
floordiv = _MathExpression() // 2
assert floordiv(5) == 2


def test_rfloordiv():
"""Ensures that add works correctly."""
floordiv = _MathExpression() // 2
rfloordiv = 5 // _MathExpression()
assert floordiv(5) == rfloordiv(2)
16 changes: 16 additions & 0 deletions tests/test_math_expression/test_mod_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_mod():
"""Ensures that add works correctly."""
mod = _MathExpression() % 3
assert mod(25) == 1


def test_rmod():
"""Ensures that add works correctly."""
mod = _MathExpression() % 3
rmod = 25 % _MathExpression()
assert mod(25) == rmod(3)
Loading