Reputation: 436
I am new to programming but I have been trying my hands at an algorithm lately. I need to perform calculations with an equation that uses all angles in a structural assembly in the coordinate plane as variables. The assembly variables, so I need this function to go through the data and combine individual equations for each two points in the assembly into the equation that is used for calculations. The part that I am confused about is how to go about combining these functions. I would like to do this in almost an "append-like" manner, if there is such a thing.
Let's say I am going through the data and at this point I have the function
a = lambda (x1,x2,x3,x4): (x1*x2)+(x3*x4)
previously generated from the first two sets, now I want to add an eq. from a third set
b = lambda (x5,x6): (x5/x6)
def new_lambda(a,b):
return lambda (x1,x2,x3,x4,x5,x6): (x1*x2)+(x3*x4)+(x5/x6)
This is a simple example of what I want to do. I'm not that familiar with anything beyond the basics of function creation so this may be a bit over my head. Feel free to refer me to general reading. If clarification is needed I can do that too.
Upvotes: 2
Views: 271
Reputation: 1265
Why not use a lambda for the combination? It's simple, explicit and some syntactic sugar helps with readability.
a = lambda x1, x2, x3, x4: (x1*x2)+(x3*x4)
b = lambda x5, x6: x5/x6
c = lambda *x: a(*x[0:4]) + b(*x[4:6])
c(1, 2, 3, 4, 10, 20)
For a bit more functional coding, the functools module is useful. For working with collections, the itertools module is good to know.
Upvotes: 1
Reputation: 32449
I will add another answer in order not to overload my first one and because this one includes both my initial idea and the valuable input from @martineau (and this time hopefully PEP-8 compliant).
Let's define a function like this and let's try to guess its arity:
import inspect
import operator
class UnguessableArityException(Exception): pass
class Function:
def __init__(self, f, arity = ...):
self.f = f
self.arity = arity
if arity == ...:
argspec = inspect.getfullargspec(f)
if argspec.varargs or argspec.kwonlyargs:
raise UnguessableArityException()
self.arity = len(argspec.args)
def __call__(self, *args):
return self.f(*args)
def op(self, g, op):
if isinstance(g, Function):
return Function(lambda *args: op(self.f(*args[:self.arity]), g.f(*args[self.arity:])), self.arity + g.arity)
return Function(lambda *args: op(self.f(*args), g), self.arity)
def rop(self, g, op):
return Function(lambda *args: op(g, self.f(*args)), self.arity)
def __add__(self, g): return self.op(g, operator.add)
def __radd__(self, g): return self.rop(g, operator.add)
def __mul__(self, g): return self.op(g, operator.mul)
def __rmul__(self, g): return self.rop(g, operator.mul)
def __truediv__(self, g): return self.op(g, operator.truediv)
def __rtruediv__(self, g): return self.rop(g, operator.truediv)
Now we basically just need to define identity:
#define identity
i = Function(lambda x: x) # λx.x
And using the identity you can start to build your "growing" ("appending") functions.
#now you can start building your function
f = i * i # λxy.x*y
print(f(3, 2))
#later in your code you need to "append" another term λxy.x*y
f = f + f # λxyza.x*y+z*a
print(f(3,2,1,4))
#even later, you need to "append" a term λxy.x/y
f = f + i / i # λxyzabc.x*y+z*a+b/c
print(f(3,2,1,4,14,2))
#works also with constants
f = 2 * f # λxyzabc.2*(x*y+z*a+b/c)
print(f(3,2,1,4,14,2))
Upvotes: 2
Reputation: 32449
I think the problem is that you need to know the arity of each function, in order to assign the arguments of the "combined" function accordingly.
Maybe you can wrap your functions in a class, something like this:
class Function:
def __init__ (self, arity, f):
self.arity = arity
self.f = f
def __call__ (self, *args):
return self.f (*args)
def __add__ (self, g):
if not isinstance (g, Function):
raise Exception ('Here be dragons')
def fg (*args):
return self.f (*args [:self.arity] ) + g.f (*args [self.arity:] )
return Function (self.arity + g.arity, fg)
a = Function (4, lambda x1, x2, x3, x4: (x1 * x2) + (x3 * x4) )
b = Function (2, lambda x1, x2: x1 / x2)
print (a (1, 2, 3, 4) )
print (b (10, 20) )
c = a + b
print (c (1, 2, 3, 4, 10, 20) )
Some words on inspection of the arity. The resulting function fg
has only one argument, namely *x
and its inspected arity will result in 0, hence prohibiting adding already added functions. Take a look here:
#Using my original class
a = Function (2, lambda x1, x2: x1 * x2)
b = Function (2, lambda x1, x2: x1 / x2)
c = a + a
print (c (1, 2, 3, 4) ) #prints 14
c = c + b
print (c (1, 2, 3, 4, 5, 6) ) #prints 14.833333333333334
Now if we use inspection like this (please correct me if you intended another use of it):
import inspect
class InspectedFunction:
def __init__ (self, f):
self.f = f
def __call__ (self, *args):
return self.f (*args)
def __add__ (self, g):
if not isinstance (g, InspectedFunction):
raise Exception ('Here be dragons')
arity = len (inspect.getargspec (self.f).args)
def fg (*args):
return self.f (*args [:arity] ) + g.f (*args [arity:] )
return InspectedFunction (fg)
a = InspectedFunction (lambda x1, x2: x1 * x2)
b = InspectedFunction (lambda x1, x2: x1 / x2)
c = a + a
print (c (1, 2, 3, 4) ) #prints 14
c = c + b
print (c (1, 2, 3, 4, 5, 6) ) #inspected arity of c is 0
will do this:
Traceback (most recent call last):
File "nomimporta.py", line 45, in <module>
print (c (1, 2, 3, 4, 5, 6) )
File "nomimporta.py", line 30, in __call__
return self.f (*args)
File "nomimporta.py", line 37, in fg
return self.f (*args [:arity] ) + g.f (*args [arity:] )
File "nomimporta.py", line 37, in fg
return self.f (*args [:arity] ) + g.f (*args [arity:] )
TypeError: <lambda>() takes exactly 2 arguments (0 given)
Upvotes: 2