Create composite function from arbitrary number of functions

Viewed 78

I am reading Joseph Howse OpenCV book. In the appendix, he's discussing creation of composite function from 2 other functions, as follows:

def createCompositeFunction(func1, func2):
    return lambda x : func2(func1(x))  

How could I write this for an arbitrary number of functions, like so

def createCompositeFunction(*funcs):
    pass

I assume this should be done using recursion, but I can not wrap my head around it. Any suggestions?

6 Answers

You don't need recursion; this is a simple iterative problem:

def createCompositeFunction(*funcs):
    def apply(x):
        for func in funcs:
            x = func(x)
        return x

    return apply


def f1(x):
    return x + 2


def f2(x):
    return x * 3


def f3(x):
    return x / 2


comp = createCompositeFunction(f1, f2, f3)

print("comp(1) =", comp(1))
print("comp(2) =", comp(2))

Running the above code will output:

comp(1) = 4.5
comp(2) = 6.0

What you're asking for in functional programming terms is a reducer higher-order function. Python provides functools.reduce to this end:

def reduce(function, iterable, initializer=None):

Where function should be an applicator, iterable is the chain of funcs you want to apply, and initializer is your argument.

Here's a simple example on one argument:

from functools import reduce

def sub1(a):
    return a - 1

def mul2(a):
    return a * 2

def apply(x, f):
    return f(x)

def compose(*fns):
    return lambda x: reduce(apply, fns, x)

print(compose(sub1, mul2)(4)) # => 6

You can partial or lambda in extra args as needed:

from functools import partial, reduce
from operator import mul, sub

def compose(*fns):
    return lambda x: reduce(lambda x, f: f(x), fns, x)

print(compose(lambda x: sub(x, 2), partial(mul, 3))(4)) # => 6

There are a lot of ways to go with this sort of thing, so I'll leave it at this absent further information about your use case.

As it turns out, this is pretty much a more fleshed-out version of Compute a chain of functions in python.

@ggorlen offers an efficient solution using reduce. Here's a recursive form -

# right-to-left composition

def compose(f = lambda x: x, *funcs):
  if not funcs:
    return f
  else:
    return lambda x: f(compose(*funcs)(x))
# left-to-right composition

def compose(f = lambda x: x, *funcs):
  if not funcs:
    return f
  else:
    return lambda x: compose(*funcs)(f(x))

Using separate definitions for identity and comp2 may make it easier to see how things are working -

def identity(x):
  return x

def comp2(f, g):
  return lambda x: f(g(x))
# right-to-left composition

def compose(f = identity, *funcs):
  if not funcs:
    return f
  else:
    return comp2(f, compose(*funcs))
# left-to-right composition

def compose(f = identity, *funcs):
  if not funcs:
    return f
  else:
    return comp2(compose(*funcs), f)

Here's a complete demo using left-to-right compose and a curry helper. Because curry accepts function as input, we can conveniently use it as a decorator too -

def curry(arity):
  def loop(f, n, args):
    return f(*args) if n == 0 else lambda x: loop(f, n - 1, (*args, x))
  return lambda f: loop(f, arity, ())

@curry(2)
def add(x, y):
  return x + y

@curry(2)
def mul(x, y):
  return x * y;
myfunc = compose(add(1), mul(2), mul(2), mul(2))

print(myfunc(0))
# (((0 + 1) * 2) * 2) * 2
# 8

@larsks has a pretty nice answer. If you're interested in recursion specifically, here's an option:

def createCompositeFunction(*funcs):
    func = funcs[0]
    funcs = funcs[1:]
    if len(funcs) == 0:
        return func
    return lambda x: func(createCompositeFunction(*funcs)(x))

def square(x):
    return x ** 2


square_thrice = createCompositeFunction(square, square, square)
print(square_thrice(2))

Output:

>>> 256

You can use accumulate from functools (and keep intermediate results):

from itertools import accumulate

def f1(x): return x + 2
def f2(x): return x * 3
def f3(x): return x / 4

def createCompositeFunction(func1, func2):
    return lambda x: func2(func1(x))

# For x=3
l = [f(3) for f in accumulate([f1, f2, f3], createCompositeFunction)]

Output:

>>> l
[5, 15, 3.75]  # <- the last item l[-1] is the final value

Recursive approached: Assumed that the range of each function is the same of the domain of the next one.


The freedom in the initial value infers a condition on the outputs of the function, it cannot be None (filter as is not None to avoid automatic casting, i.e. 1<-->True, ''<-->False, ...).

def direct_composition(funcs, init_value=None):
    if funcs:
        if init_value is not None:
            return direct_composition(funcs[1:], funcs[0](init_value))
        return direct_composition(funcs[1:], funcs[0]())
    return init_value

# sample functions
def a0(): return 'a' # initial function with no args
def a(x): return 'a'+x
def b(x): return 'b' + x
def c(x): return 'c' + x

# test with initial function taking parameters
funcs = a, b, c
direct_composition(funcs, '>')
#cba>

# test with initial function taking no parameters
funcs = a0, a, b, c
direct_composition(funcs)
#cba

Double layer approach with no side-effects, no restriction on the output of the functions. A pushward is when you fix a function that will be the most internal one and the other functions will be applied in increasing order to it.

def pushforward(f, initial_value=None):
    def apply(value, funcs):
        if funcs:
            return apply(funcs[0](value), funcs[1:])
        return value
    return (lambda funcs: apply(f(initial_value), funcs)) if initial_value else (lambda funcs: apply(f(), funcs))


# with no initial value
f_init = a0
funcs = a, b, c
res = pushforward(f_init)(funcs)
print(res)

# with initial value
f_init = a
funcs = b, c
res = pushforward(f_init, '>')(funcs)
print(res)
Related