Last active
March 6, 2024 13:59
-
-
Save phagenlocher/8532fe65a5bc99cb5f835441c950baf6 to your computer and use it in GitHub Desktop.
Lazy Evaluation in Python (https://youtu.be/KniIeHiEzdo)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Lazy: | |
def __init__(self, fun, *args, **kwargs): | |
self.fun = fun | |
self.args = args | |
self.kwargs = kwargs | |
self.executed = False | |
self.value = None | |
# Binary Operators | |
def __binary__(self, binfun, other): | |
def fun(self, other): | |
if type(other) == Lazy: | |
other = other.__force__() | |
return binfun(self.__force__(), other) | |
return Lazy(fun, self, other) | |
def __add__(self, other): | |
return self.__binary__(lambda x,y: x+y, other) | |
def __mul__(self, other): | |
return self.__binary__(lambda x,y: x*y, other) | |
def __sub__(self, other): | |
return self.__binary__(lambda x,y: x-y, other) | |
def __truediv__(self, other): | |
return self.__binary__(lambda x,y: x/y, other) | |
def __floordiv__(self, other): | |
return self.__binary__(lambda x,y: x//y, other) | |
def __mod__(self, other): | |
return self.__binary__(lambda x,y: x%y, other) | |
def __pow__(self, other): | |
return self.__binary__(lambda x,y: x**y, other) | |
def __or__(self, other): | |
return self.__binary__(lambda x,y: x or y, other) | |
def __and__(self, other): | |
return self.__binary__(lambda x,y: x and y, other) | |
def __xor__(self, other): | |
return self.__binary__(lambda x,y: x^y, other) | |
def __lshift__(self, other): | |
return self.__binary__(lambda x,y: x<<y, other) | |
def __rshift__(self, other): | |
return self.__binary__(lambda x,y: x>>y, other) | |
# Comparisons Operators | |
def __lt__(self, other): | |
return self.__binary__(lambda x,y: x<y, other) | |
def __gt__(self, other): | |
return self.__binary__(lambda x,y: x>y, other) | |
def __le__(self, other): | |
return self.__binary__(lambda x,y: x<=y, other) | |
def __ge__(self, other): | |
return self.__binary__(lambda x,y: x>=y, other) | |
def __eq__(self, other): | |
return self.__binary__(lambda x,y: x==y, other) | |
def __ne__(self, other): | |
return self.__binary__(lambda x,y: x!=y, other) | |
# Unary Operators | |
def __unary__(self, unfun): | |
return Lazy(lambda x : unfun(x.__force__()), self) | |
def __bool__(self): | |
# __bool__ has to return a boolean --> force needed | |
return self.__unary__(bool).__force__() | |
def __str__(self): | |
# __str__ has to return a string --> force needed | |
return self.__unary__(str).__force__() | |
def __pos__(self): | |
return self.__unary__(lambda x:+x) | |
def __neg__(self): | |
return self.__unary__(lambda x:-x) | |
def __invert__(self): | |
return self.__unary__(lambda x:~x) | |
def __repr__(self): | |
if self.executed: | |
val = repr(self.value) | |
else: | |
val = "_" | |
return "Lazy({}) at {}".format(val, hex(id(self))) | |
def __force__(self): | |
if not self.executed: | |
self.value = self.fun(*self.args, **self.kwargs) | |
self.executed = True | |
return self.value | |
def lazy(fun_or_val, *args, **kwargs): | |
if len(args) == 0 and len(kwargs) == 0: | |
# This is a single value, not a function | |
lazy_object = Lazy(lambda x:x, fun_or_val) | |
lazy_object.__force__() | |
return lazy_object | |
return Lazy(fun_or_val, *args, **kwargs) | |
def force(lazy_object): | |
if type(lazy_object) != Lazy: | |
return lazy_object | |
return lazy_object.__force__() | |
def lazyif(condition, tbranch, fbranch): | |
return lazy(lambda x : tbranch if force(x) else fbranch, condition) | |
def lazyfac(n): | |
if n <= 1: | |
return lazy(1) | |
else: | |
return lazyfac(n-1) * n | |
def lazyfac2(n): | |
if n <= 1: | |
return lazy(1) | |
else: | |
return lazy(lambda x : force(lazyfac(x-1) * x), n) | |
# Note that it's not important wether we use lazyfac2 or lazyfac here. | |
# Upon evaluation it makes no difference wether we defer the construction | |
# of the term or not since it is being evaluated. | |
# sys.setrecursionlimit(5) | |
from itertools import permutations | |
def slow(n): | |
l = list(range(n)) | |
count = 0 | |
for p in permutations(l): | |
if all([i != j for (i,j) in zip(l,p)]): | |
count += 1 | |
return count | |
### Infinite "lists" | |
def lgen(f, step, start): | |
n = start | |
while True: | |
yield f(n) | |
n = step(n) | |
def lmap(f, gen): | |
for n in gen: | |
yield f(n) | |
def lfilter(p, gen): | |
for n in gen: | |
if p(n): | |
yield n | |
else: | |
continue | |
def ltake(c, gen): | |
for n in gen: | |
yield n | |
c -= 1 | |
if c <= 0: | |
break | |
def ltakewhile(p, gen): | |
for n in gen: | |
if p(n): | |
yield n | |
else: | |
break | |
g = lgen(lambda x : x, lambda x : x+1, 0) | |
m = lmap(lambda x : x*x, g) | |
f = lfilter(lambda x : x%2==0, m) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment