Python avoiding lambda for key which needs two cal

2019-01-15 19:56发布

问题:

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?

回答1:

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:

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