5
votes

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?

2
this is the perfect case for an anonymous function. Just use it - JBernardo
@JBernardo No, it's a perfect case for a higher-order function/combinator. - user395760
@Strigoides Using a lambda to avoid using a lambda? I'm not sure the OP will buy that :) - Lev Levitsky
compose = lambda *funcs: lambda x: reduce(lambda y, f: f(y), reversed(funcs), x) (compose() is not recommended to use whatever implementation (the code becomes less readable)) - jfs
@J.F.Sebastian Language may, to an extent, dictate style, but there's nothing inherent in a python pointfree programme that renders it less readable than e.g. in Haskell. Yes, the python programmers who will find that convenient are a subset of all python programmers, but that is also true of completely idiomatic code. - Marcin

2 Answers

6
votes

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)
2
votes

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