I was working on project euler problem 14 and as a first attempt I whipped up this brute-force solution:
def collatz(n, memo={1: [1]}):
if n not in memo:
memo[n] = [n] + collatz(3 * n + 1 if n % 2 else n // 2)
return memo[n]
def p014():
return max(xrange(1, 10**6), key=lambda n: len(collatz(n)))
My question is about that lambda, I'm usually reluctant to use them but I don't know any elegant way to avoid it in this case. Is there something in functools
or other to chain two callables, or any other neat alternative I'm missing?
It would be lovely if there were a compose
function -- perhaps in functools
. There is not, nor do I expect there will be, alas. In the words of Raymond Hettinger,
It has been previously discussed and rejected in other forums. One of the issues is that the usual mathematical order is unintuitive and not self-documenting -- i.e. is compose(f,g)
the same as f(g(x))
or g(f(x))
? Also, it is already dirt simple to create your own compose function or to do the composition directly: h = lambda x: f(g(x))
.
Here are two simple implementations of compose
as a callable class that you may find useful though:
# Scott Daniels, http://code.activestate.com/recipes/52902-function-composition/
# Lightly edited for style.
class Compose(object):
'''Compose functions. compose(f,g,x...)(y...) = f(g(y...),x...))'''
def __init__(self, f, g, *args, **kwargs):
self.f = f
self.g = g
self.pending = args[:]
self.kwargs = kwargs.copy()
def __call__(self, *args, **kwargs):
return self.f(self.g(*args, **kwargs), *self.pending, **self.kwargs)
class Starcompose:
'''Compose functions. Starcompose(f,g,x...)(y...) = f(*g(y...),x...))'''
TupleType = type(())
def __init__(self, f, g, *args, **kwargs):
self.f = f
self.g = g
self.pending = args[:]
self.kwargs = kwargs.copy()
def __call__(self, *args, **kwargs):
mid = self.g(*args, **kwargs)
if isinstance(mid, self.TupleType):
return self.f(*(mid + self.pending), **self.kwargs)
return self.f(mid, *self.pending, **self.kwargs)
Also, see the functional
package, which inspired this very simple compose_many
function of mine a while ago:
def compose(f1, f2):
def composition(*args, **kwargs):
return f1(f2(*args, **kwargs))
return composition
def compose_many(*funcs):
return reduce(compose, funcs)
It might be more pythonic to write this as a generator:
def p014():
length, n = max(
(len(collatz(n)), n)
for n in xrange(1, 10**6)
)
return n