i don't know it is dynamic programming - python

i am beginner who learning algorithm in korea college.
it is assignment that solving the problem by using dynamic programming without recursive programming, using only python.
the problem is whether the result is printed out 'a' by some operation or not when you input 5 alphabet of string that composed only 'a','b','c'
I made it, but I'm not sure it's dynamic programming
i really appreciate your favor if you give me a advice
Issue.
a b c
a b b a
b c b a
c a c c
There is an arithmetic table consisting of three elements, {a, b, and c} and the row, column, and corresponding intersecting values of the table give you a value such as {aa = b, ab = b, ac=a}.
Based on these values, print out whether or not the result can be derived for any string (' possible' or 'impossible'),
and, if possible, write the calculation sequence using a dynamic programming technique to indicate the sequence through the bracket.
example
input : bbbba.
Result: possible, (b(bb)(bb)a))
In the example above the table are computed by referring to the first round brackets and Analysis,
(bb) is (b).
Then (b(b)) becomes (b) , and (b)a becomes c, indicating that
the final requirement of bc=a is met.
- The language must use the Python.
- dynamic programming algorithms are techniques allowed for (dont' use a recursion x).
- Output the first value obtained in many cases
- Set the length of the string, which is the input value, to 5.
def rule(x,y):
if((x=='a' and y=='c') or (x=='b' and y=='c') or (x=='c' and y=='a')):
return 'a'
if((x=='a' and y=='a') or (x=='a' and y=='b') or (x=='b' and y=='b')):
return 'b'
if((x=='b' and y=='a') or (x=='c' and y=='b') or (x=='c' and y=='c')):
return 'c'
def rule2(x,y,z):
return rule(rule(x,y),z)
def rule3(x,y,z):
return rule(x,rule(y,z))
def rule4(w,x,y,z):
return rule(rule(w,x),rule(y,z))
def rule5(w,x,y,z):
return rule(rule2(w,x,y),z)
def rule6(w,x,y,z):
return rule(rule3(w,x,y),z)
def rule7(w,x,y,z):
return rule(w,rule2(x,y,z))
def rule8(w,x,y,z):
return rule(w,rule3(x,y,z))
def rule9(v,w,x,y,z):
global k
k='((('+v+w+')'+x+')'+y+z+')'
return rule(rule2(v,w,x),rule(y,z))
def rule10(v,w,x,y,z):
global k
k='(('+v+'('+w+x+'))('+y+z+'))'
return rule(rule3(v,w,x),rule(y,z))
def rule11(v,w,x,y,z):
global k
k='(('+v+w+')(('+x+y+')'+z+'))'
return rule(rule(v,w),rule2(x,y,z))
def rule12(v,w,x,y,z):
global k
k='(('+v+w+')('+x+'('+y+z+')))'
return rule(rule(v,w),rule3(x,y,z))
def rule13(v,w,x,y,z):
global k
k='((('+v+w+')('+x+y+'))'+z+')'
return rule(rule4(v,w,x,y),z)
def rule14(v,w,x,y,z):
global k
k='(((('+v+w+')'+x+')'+y+')'+z+')'
return rule(rule5(v,w,x,y),z)
def rule15(v,w,x,y,z):
global k
k='((('+v+'('+w+x+')'+y+')'+z+'))'
return rule(rule6(v,w,x,y),z)
def rule16(v,w,x,y,z):
global k
k='('+v+'('+w+'(('+x+y+')'+z+')))'
return rule(rule7(v,w,x,y),z)
def rule17(v,w,x,y,z):
global k
k='('+v+'('+w+'('+x+'('+y+z+'))))'
return rule(rule8(v,w,x,y),z)
def rule18(v,w,x,y,z):
global k
k='('+v+'(('+w+x+')('+y+z+')))'
return rule(v,rule4(w,x,y,z))
def rule19(v,w,x,y,z):
global k
k='(('+v+'(('+w+x+')'+y+')'+z+'))'
return rule(v,rule5(w,x,y,z))
def rule20(v,w,x,y,z):
global k
k='('+v+'(('+w+'('+x+y+'))'+z+'))'
return rule(v,rule6(w,x,y,z))
def rule21(v,w,x,y,z):
k='('+v+'('+w+'(('+x+y+')'+'))'+z+')'
return rule(v,rule7(w,x,y,z))
def rule22(v,w,x,y,z):
global k
k='('+v+'('+w+'('+x+'('+y+z+'))))'
return rule(v,rule8(w,x,y,z))
def rule23(v,w,x,y,z):
global k
k='((('+v+w+')'+x+')'+'('+y+z+'))'
return rule2(rule(v,w),x,rule(y,z))
def rule24(v,w,x,y,z):
global k
k='(('+v+w+')('+x+'('+y+z+')))'
return rule3(rule(v,w),x,rule(y,z))
print(" input:",end='')
str=input()
str=list(str)
x=[rule9,rule10,rule11,rule12,rule13,rule14,rule15,rule16,rule17,rule18,rule19,
rule20,rule21,rule22,rule23,rule24]
for i in range(0,16):
y=x[i](str[0],str[1],str[2],str[3],str[4])
if(y=='a'):
print("possible,",end=' ')
print(k)
break
if(y!='a' and i==15):
print("impossible")

Of course there can be better way to solve the problem you approached and probably you will learn that once you will see other people assignment solutions, but about recursive/dynamic question you had, your code execution in memory is not recursive.
It is definitely a linear dynamic programming what you have created up there.
Just to be sure, you could also test it via this implementation of a recursive test: https://stackoverflow.com/a/36663046/3564632
So overall you might try with this example just to test it out:
from bdb import Bdb
import sys
class RecursionDetected(Exception):
pass
class RecursionDetector(Bdb):
def do_clear(self, arg):
pass
def __init__(self, *args):
Bdb.__init__(self, *args)
self.stack = set()
def user_call(self, frame, argument_list):
code = frame.f_code
if code in self.stack:
raise RecursionDetected
self.stack.add(code)
def user_return(self, frame, return_value):
self.stack.remove(frame.f_code)
def test_recursion(func):
detector = RecursionDetector()
detector.set_trace()
try:
func()
except RecursionDetected:
return True
else:
return False
finally:
sys.settrace(None)
def rule(x,y):
if((x=='a' and y=='c') or (x=='b' and y=='c') or (x=='c' and y=='a')):
return 'a'
if((x=='a' and y=='a') or (x=='a' and y=='b') or (x=='b' and y=='b')):
return 'b'
if((x=='b' and y=='a') or (x=='c' and y=='b') or (x=='c' and y=='c')):
return 'c'
def rule2(x,y,z):
return rule(rule(x,y),z)
def rule3(x,y,z):
return rule(x,rule(y,z))
def rule4(w,x,y,z):
return rule(rule(w,x),rule(y,z))
def rule5(w,x,y,z):
return rule(rule2(w,x,y),z)
def rule6(w,x,y,z):
return rule(rule3(w,x,y),z)
def rule7(w,x,y,z):
return rule(w,rule2(x,y,z))
def rule8(w,x,y,z):
return rule(w,rule3(x,y,z))
def rule9(v,w,x,y,z):
global k
k='((('+v+w+')'+x+')'+y+z+')'
return rule(rule2(v,w,x),rule(y,z))
def rule10(v,w,x,y,z):
global k
k='(('+v+'('+w+x+'))('+y+z+'))'
return rule(rule3(v,w,x),rule(y,z))
def rule11(v,w,x,y,z):
global k
k='(('+v+w+')(('+x+y+')'+z+'))'
return rule(rule(v,w),rule2(x,y,z))
def rule12(v,w,x,y,z):
global k
k='(('+v+w+')('+x+'('+y+z+')))'
return rule(rule(v,w),rule3(x,y,z))
def rule13(v,w,x,y,z):
global k
k='((('+v+w+')('+x+y+'))'+z+')'
return rule(rule4(v,w,x,y),z)
def rule14(v,w,x,y,z):
global k
k='(((('+v+w+')'+x+')'+y+')'+z+')'
return rule(rule5(v,w,x,y),z)
def rule15(v,w,x,y,z):
global k
k='((('+v+'('+w+x+')'+y+')'+z+'))'
return rule(rule6(v,w,x,y),z)
def rule16(v,w,x,y,z):
global k
k='('+v+'('+w+'(('+x+y+')'+z+')))'
return rule(rule7(v,w,x,y),z)
def rule17(v,w,x,y,z):
global k
k='('+v+'('+w+'('+x+'('+y+z+'))))'
return rule(rule8(v,w,x,y),z)
def rule18(v,w,x,y,z):
global k
k='('+v+'(('+w+x+')('+y+z+')))'
return rule(v,rule4(w,x,y,z))
def rule19(v,w,x,y,z):
global k
k='(('+v+'(('+w+x+')'+y+')'+z+'))'
return rule(v,rule5(w,x,y,z))
def rule20(v,w,x,y,z):
global k
k='('+v+'(('+w+'('+x+y+'))'+z+'))'
return rule(v,rule6(w,x,y,z))
def rule21(v,w,x,y,z):
k='('+v+'('+w+'(('+x+y+')'+'))'+z+')'
return rule(v,rule7(w,x,y,z))
def rule22(v,w,x,y,z):
global k
k='('+v+'('+w+'('+x+'('+y+z+'))))'
return rule(v,rule8(w,x,y,z))
def rule23(v,w,x,y,z):
global k
k='((('+v+w+')'+x+')'+'('+y+z+'))'
return rule2(rule(v,w),x,rule(y,z))
def rule24(v,w,x,y,z):
global k
k='(('+v+w+')('+x+'('+y+z+')))'
return rule3(rule(v,w),x,rule(y,z))
print(" input:",end='')
str=input()
str=list(str)
x=[rule9,rule10,rule11,rule12,rule13,rule14,rule15,rule16,rule17,rule18,rule19,
rule20,rule21,rule22,rule23,rule24]
for i in range(0,16):
assert not test_recursion(lambda: x[i](str[0],str[1],str[2],str[3],str[4]))
y = x[i](str[0],str[1],str[2],str[3],str[4])
if(y=='a'):
print("possible,",end=' ')
print(k)
break
if(y!='a' and i==15):
print("impossible")

Related

Trying to create a function that repeats a function

I have a helping function:
def incr(x):
return x+1
I want to create a function named "repeated" that use "incr" function n times on a certain parameter
In the end I want to use the "repeated" function in this matter only :
repeated (incr, 4)(2)
That for example will output 6.
So far I tried to do this:
def repeated(f, n):
func, x = f
for i in range(n):
func(x)
But it gave me an error saying I can't unpack a non Tuple function.
It doesn't seem like I don't have access in the function to the "(2)"
I do not recommend to use such a syntax construct, for such a task:
repeated(incr, 4)(2)
Your repeated function must return another function, that will be called by (2).
This should work in your requested manner:
def incr(x):
return x+1
def repeated(f, x):
# function foo will be returned by repeated and called by (2)
def foo(n):
res = x
for i in range(n):
res = f(res)
return res
return foo
print(repeated(incr, 4)(2))
I think you may want to do something like functional programming.
Add args to deal with for different kind of function you want to repeat.
I can't confirm if there is a position argument what kind of results you want, so I didn't deal with it.
code:
import functools
def incr(x):
return x + 1
def incx(x,y = 0):
return x + y + 1
def repeated_inner(func,args,times):
head, *rest = args
for _ in range(times):
head = func(head, *rest)
return args[0]
def repeated(func, *args ):
return functools.partial(repeated_inner, func, args)
print(repeated(incr, 4)(2))
print(repeated(incx, 4)(2))
print(repeated(incx, 4 ,3)(2))
result
6
6
12
the repeatedfunction must return a function
def repeated(func, n):
def repeatedfunc(x):
rsl = x
for i in range(n):
rsl = func(rsl)
return rsl
return repeatedfunc
def incr(x):
return x+1
rslt = repeated(incr, 4)(2)
print(rslt)
output
6
You should write something like this:
def repeated(f, arg_0, n):
arg = arg_0
for i in range(n):
arg = f(arg)
return arg
In a more general situation:
def repeated(f, arg):
def n_f(n):
result = 0
for i in range(n):
result =f(arg)
return result
return n_f

Don't understand this "Recursion depth exceeded" error for singly linked list implementation

I'm having a little bit of trouble running this code in linux for my class. This week we're on singly linked list and the assignment my teacher gave me was to use nodes to represent polynomials and list them in descending order I keep coming across a maximum recursion depth exceed error in my Node class.
Here is the code for Node:
#!/usr/bin/python
import sys
sys.setrecursionlimit(4500)
"""A model containing Node class"""
class Node(object):
"""A single node in a data structure"""
def __init__(self, coefficient, exponent):
self.coefficient=coefficient
self.exponent=exponent
#property
def coefficient(self):
return self.coefficient
#coefficient.setter
def coefficient(self, c):
self.coefficient=c
#coefficient.deleter
def coefficient(self):
del self.coefficient
#property
def exponent(self):
return self.exponent
#exponent.setter
def exponent(self, e):
self.exponent=e
#exponent.deleter
def exponent(self):
del self.exponent
#property
def next(self):
return self.next
#next.setter
def next(self, n):
self.next=n
#next.deleter
def next(self):
del self.next
def __eq__(self, other):
if self.exponent==other.exponent:
return True
else:
return False
def __It__(self, other):
if self.exponent<other.exponent:
return True
else:
return False
def __str(self):
if self.coefficient>=0:
sign="+"
else:
sign=""
return sign +str(self.coefficient) + "X^" +str(self.exponent)
Here is the code for my List class:
#!/usr/bin/python
from NodeModule import Node
class List(Node):
"""Linked list with pre-defined Node class"""
def __init__(self):
self.head=None
self.count=0
def isEmpty(self):
return self.count==0
def getSize(self):
return self.count
def insert(self, index, o, p):
if index<0 or index > self.count:
return False
n=Node(o, p)
if index==0:
n.next=self.head
self.head=n
self.count+=1
return True
walker=self.head
for i in range(index-1):
walker=walker.next
n.next=walker.next
walker.next=n
self.count+=1
return True
def delete(self, index):
if index < 0 or index > self.count:
return False
if index==0:
self.head=self.head.next
self.count-=1
return True
walker=self.head
for i in range(index-1):
walker=walker.next
walker.next=walker.next.next
self.count-=1
return True
def sort(self):
temp1=self.head.exponent
walker=self.head
j=0
while j < self.count:
for i in self.getsize():
walker=walker.next
temp2=walker.next.exponent
if walker.next.exponent > temp1:
insert(0, walker.next.coefficient, walker.next.exponent)
delete(walker.next)
while walker.next is not None:
if walker.next.exponent < walker.next.next.exponent:
insert(self.getsize(), walker.next.next.coefficient, walker.next.next.exponent)
delete(walker.next)
j+=1
def str(self):
if self.isEmpty():
return "\nEnd of Polynomial"
walker=self.head
output=[]
while walker is not None:
output.append(str(walker))
walker=walker.next
return " + " .join(output)
And here's what I'm using to test my code:
#!/usr/bin/python
from NodeModule import Node
from ListModule import List
def readPoly(message):
l=List()
n=input(message)
for i in range(n):
c=input("Enter the coefficient of term %d" % i)
e=input("Enter the exponent of term %d" % i)
l.insert(0, Node(c,e))
return l
def main():
l=readPoly("Enter the number of terms of the polynomial: ")
print l
l.sort()
print l
l.delete(0)
print (l)
if __name__=='__main__':
main()
The interpreter is telling me that the error is on my self.coefficient=c line in my Node class. How can I go about fixing this issue?
You should not have a variable called coefficient and a property with the same name.
Look at your definition of the setter:
#coefficient.setter
def coefficient(self, c):
self.coefficient=c
When calling self.coefficient = c, the same setter will be called again, recursively.
To solve it, you could rename your variable with an underscore (and change all the other parts of your code):
#coefficient.setter
def coefficient(self, c):
self._coefficient = c
On further reading , it looks like you could omit the setters altogether, since they do not serve a purpose in your code. A reduced version of your class Node (with the same functionality) could be:
class Node:
def __init__(self, coefficient, exponent):
self.coefficient = coefficient
self.exponent = exponent
self.next = None
def __str__(self):
return '{}{}X^{}'.format(
'+' if self.coefficient >= 0 else '',
self.coefficient,
self.exponent)

Scrambling numbers

I am trying to program an algorithm that scrambles and "unscrambles" integer numbers.
I need two functions forward and backward
backward(number): return a "random" number between 0 and 9, the same input number always returns the same output
forward(number): return the input to backward that returns number
I managed to solve the problem like this:
from random import randint
class Scrambler:
def __init__(self):
self.mapping = [i for i in range(10)]
# scramble mapping
for i in range(1000):
r1 = randint(0, len(self.mapping) - 1)
r2 = randint(0, len(self.mapping) - 1)
temp = self.mapping[r1]
self.mapping[r1] = self.mapping[r2]
self.mapping[r2] = temp
def backward(self, num):
return self.mapping[num]
def forward(self, num):
return self.mapping.index(num)
if __name__ == '__main__':
s = Scrambler()
print(s.mapping)
for i in range(len(s.mapping)):
print(i, s.forward(i), s.backward(i), s.forward(s.backward(i)), s.backward(s.forward(i)))
Is there a way to do this without using the mapping list?
Can i calculate the return value of the functions forward and backward?
The "randomness" of the numbers does not need to be perfect.
I think your current solution is better than coming up with a function each time. It is a good solution.
Here is a generic solution for a generic key. You'd make your version using the Cipher.random_range method I've stuck on.
import random
class Cipher:
def __init__(self, key):
"""
key is a dict of unique values (i.e. bijection)
"""
if len(set(key.values())) != len(key):
raise ValueError('key values are not unique')
self._encoder = key.copy()
self._decoder = {v: k for k, v in key.items()}
#classmethod
def random_range(cls, max):
lst = list(range(max))
random.shuffle(lst)
return cls(dict(enumerate(lst)))
def encode(self, num):
return self._encoder[num]
def decode(self, num):
return self._decoder[num]

Delayed Compute with Method Chaining in Python

Let say I have a class:
class MATH(object):
def __init__(self):
self.results = [0, 1, 2]
def add(self, value):
# Add amount 'value' to every element in the results list
def minus(self, value):
# Subtract amount 'value' from every element in the results list
def compute(self):
# Perform computation
Is there a way to do something like:
m = MATH()
m.add(5).minus(2).add(7) # This would be a lazy and not actually compute
m.compute() # This would actually run the computations in order
How do I do something like this in python?
Personally, I would have .add(), et al, push the operator and the operand onto a list and then have .compute() walk through the list, computing the answer as it goes.
Operator chaining is easily accomplished by having each operator return self as its final instruction.
For example:
class MATH(object):
def __init__(self):
self.results = [0, 1, 2]
self.operations = []
def add(self, value):
# Add amount 'value' to every element in the results list
self.operations.append(('+', value))
return self
def minus(self, value):
# Subtract amount 'value' from every element in the results list
self.operations.append(('-', value))
return self
def compute(self):
results = []
for x in self.results:
for op, value in self.operations:
if op == '+':
x += value
elif op == '-':
x -= value
results.append(x)
return results
m = MATH()
m.add(5).minus(2).add(7) # This would be a lazy and not actually compute
print(m.compute()) # This would actually run the computations in order
Wow, you guys are fast!
Here is another go also with a stack, but manipulating the results-list:
class MATH(object):
def __init__(self):
self.results = [0, 1, 2]
self.stack = []
def add(self, value):
self.stack.append(value)
return self
def minus(self, value):
self.stack.append(-value)
return self
def compute(self):
for s in self.stack:
for index, _ in enumerate(self.results):
self.results[index] += s
m = MATH()
m.add(5).minus(2).add(7) # This would be a lazy and not actually compute
m.compute() # This would actually run the computations in order
print m.results
[10, 11, 12]
As #Rob pointed out, you will need some way to store the operators so that the final compute method can be utilized correctly. This solution uses __add__ and __sub__, with a decorator to store the operators. Note, however, that it would be much more efficient to keep a running total of the values that have been pushed to the stack:
import operator as op
from collections import deque
def operator(f):
def wrapper(cls, _):
cls.operators.append(f.__name__.replace('__', ''))
return f(cls, _)
return wrapper
class Math:
def __init__(self):
self.stack = []
self.operators = deque()
#operator
def __sub__(self, _val):
self.stack.append(_val)
return self
#operator
def __add__(self, _val):
self.stack.append(_val)
return self
def compute(self):
_result = 0
while self.stack:
a, *c = self.stack
_result = getattr(op, self.operators.popleft())(_result, a)
self.stack = c
return _result
m = Math()
m1 = m + 5 - 2 + 7
print([m1.stack, m1.operators])
print(m1.compute())
Output:
[[5, 2, 7], ['add', 'sub', 'add']]
10
Here's a string-based approach which requires little brainpower.
class Math:
def __init__(self):
self.stack = '0'
#staticmethod
def wrap(expr):
return '(' + expr + ')'
def _op(self, other, op):
self.stack = ' '.join([Math.wrap(self.stack), op, str(other)])
def add(self, other):
self._op(other, '+')
return self
def mul(self, other):
self._op(other, '*')
return self
def compute(self):
return eval(self.stack)
m = Math()
print(m.add(2).mul(3).compute())

Avoiding unnecessary key evaluations when sorting a list

I have a list which I want to sort by multiple keys, like:
L = [ ... ]
L.sort(key = lambda x: ( f(x), g(x) ))
This works fine. However, this results with unnecessary calls to g, which I would like to avoid (for being potentially slow). In other words, I want to partially and lazily evaluate the key.
For example, if f is unique over L (i.e. len(L) == len(set(map(f,L)))) no calls to g should be made.
What would be the most elegant/pythonic way to do this?
One way I can think of is to define a custom cmp function (L.sort(cmp=partial_cmp)), but IMO this is less elegant and more complicated than using the key parameter.
Another way would be to define a key-wrapper class which takes a generator expression to generate the different parts of the key, and override the comparison operators to compare one-by-one. However, I'm feeling there must be a simpler way...
EDIT: I'm interested in a solution for the general problem of sorting by multiple functions, not only two as in my example above.
You can try using itertools.groupby:
result = []
for groupKey, group in groupby(sorted(L, key=f), key=f):
sublist = [y for y in group]
if len(sublist) > 1:
result += sorted(sublist, key=g)
else:
result += sublist
Another possibility, even less elegant, but in place:
L.sort(key = f)
start = None
end = None
for i,x in enumerate(L):
if start == None:
start = i
elif f(x) == f(L[start]):
end = i
elif end == None:
start = i
else:
L[start:end+1] = sorted(L[start:end+1], key=g)
start = None
if start != None and end != None:
L[start:end+1] = sorted(L[start:end+1], key=g)
First version generalized to any number of functions:
def sortBy(l, keyChain):
if not keyChain:
return l
result = []
f = keyChain[0]
for groupKey, group in groupby(sorted(l, key=f), key=f):
sublist = [y for y in group]
if len(sublist) > 1:
result += sortBy(sublist, keyChain[1:])
else:
result += sublist
return result
The second version generalized to any number of functions (not fully in place though):
def subSort(l, start, end, keyChain):
part = l[start:end+1]
sortBy(part, keyChain[1:])
l[start:end+1] = part
def sortBy(l, keyChain):
if not keyChain:
return
f = keyChain[0]
l.sort(key = f)
start = None
end = None
for i,x in enumerate(l):
if start == None:
start = i
elif f(x) == f(l[start]):
end = i
elif end == None:
start = i
else:
subSort(l, start, end, keyChain)
start = i
end = None
if start != None and end != None:
subSort(l, start, end, keyChain)
Given a function, you could create a LazyComparer class like this:
def lazy_func(func):
class LazyComparer(object):
def __init__(self, x):
self.x = x
def __lt__(self, other):
return func(self.x) < func(other.x)
def __eq__(self, other):
return func(self.x) == func(other.x)
return lambda x: LazyComparer(x)
To make a lazy key function out of multiple functions, you could create a utility function:
def make_lazy(*funcs):
def wrapper(x):
return [lazy_func(f)(x) for f in funcs]
return wrapper
And together they could be used like this:
def countcalls(f):
"Decorator that makes the function count calls to it."
def _f(*args, **kwargs):
_f._count += 1
return f(*args, **kwargs)
_f._count = 0
return _f
#countcalls
def g(x): return x
#countcalls
def f1(x): return 0
#countcalls
def f2(x): return x
def report_calls(*funcs):
print(' | '.join(['{} calls to {}'.format(f._count, f.func_name)
for f in funcs]))
L = range(10)[::-1]
L.sort(key=make_lazy(f1, g))
report_calls(f1, g)
g._count = 0
L.sort(key=make_lazy(f2, g))
report_calls(f2, g)
which yields
18 calls to f1 | 36 calls to g
36 calls to f2 | 0 calls to g
The #countcalls decorator above is used to connfirm that when f1 returns a lot
of ties, g is called to break the ties, but when f2 returns distinct values,
g does not get called.
NPE's solution adds memoization within the Key class. With the solution above,
you could add memoization outside (independent of) the LazyComparer class:
def memo(f):
# Author: Peter Norvig
"""Decorator that caches the return value for each call to f(args).
Then when called again with same args, we can just look it up."""
cache = {}
def _f(*args):
try:
return cache[args]
except KeyError:
cache[args] = result = f(*args)
return result
except TypeError:
# some element of args can't be a dict key
return f(*args)
_f.cache = cache
return _f
L.sort(key=make_lazy(memo(f1), memo(g)))
report_calls(f1, g)
which results in fewer calls to g:
10 calls to f1 | 10 calls to g
You could use a key object that would lazily evaluate and cache g(x):
class Key(object):
def __init__(self, obj):
self.obj = obj
self.f = f(obj)
#property
def g(self):
if not hasattr(self, "_g"):
self._g = g(self.obj)
return self._g
def __cmp__(self, rhs):
return cmp(self.f, rhs.f) or cmp(self.g, rhs.g)
Here is an example of use:
def f(x):
f.count += 1
return x // 2
f.count = 0
def g(x):
g.count += 1
return x
g.count = 0
L = [1, 10, 2, 33, 45, 90, 3, 6, 1000, 1]
print sorted(L, key=Key)
print f.count, g.count

Categories