Nested loop on a list with a function - python

I got stuck on that:
a list y = (1,2,3...)
a function func(A,B)
a constant C
How can I express this situation with loops?
B1 = func(C , y[0])
B2 = func(B1 , y[1])
B3 = func(B2 , y[2]) #.... and so on.

The first argument is just the return value of the previous call, starting with C:
result = C
for yval in y:
result = func(result, yval)
As pointed out in the comments, this pattern is captured by the often overlooked reduce function. (Overlooked, in part, because it was demoted from the built-in namespace to the functools module in Python 3.)
from functools import reduce
result = reduce(func, y, C)

There are a few ways to do this:
First of all, regular loop:
C = ... # the constant
b = C
for i in y:
b = func(b, i)
But using a reduce like this, is my preferred way of doing this:
from functools import reduce
b = reduce(func, y, C) # the last arg being the initial item used
You could also use the walrus notation, which is only useful (IMO) when saving the intermediate states.
bs = [(b := func(b, yi)) for yi in y)]
b # b being the end result

Related

Combine list of lambdas in a single one

What's the pythonic way to combine a list of lambdas in a single function? For example:
lambdas = [lambda x, k=k: x+k for k in range(3)]
I would like to get this all in a single lambda similar to this but without having to type it out:
f = lambda x: lambdas[2](lambdas[1](lambdas[0](x)))
You can do this with functools.reduce like below:
from functools import reduce
lambdas = [lambda x, k=k: x+k for k in range(3)]
# x = 0
reduce(lambda x, l: l(x), lambdas, x)
# -> l[2](l[1](l[0](x)))
# step_1 : x = x , l = lambda[0] -> lambda[0](x)
# step_2 : x = lambda[0](x), l = lambda[1] -> lambda[1](lambda[0](x))
# step_3 : x = lambda[1](lambda[0](x)), l = lambda[2] -> lambda[2](lambda[1](lambda[0](x)))
The reduce function is defined to be exactly what you want. An alternative is to use a simple for loop.
def f(x):
for func in lambdas:
x = func(x)
return x
to do this with a lambda seems kind of weird.
Is there any specific reason why we cannot:
def function_chainer(lambdas):
def chained(x):
for function in lambdas:
x = function(x)
return chained
This solution is not a one-liner, but it is pythonic I believe.
If you really need a one-liner, you can use functools.reduce:
lambda x: functools.reduce(lambda a, f: f(a), lambdas, x)
The first argument to reduce governs the way of applying each subsequent element, the second is the iterable (here - our iterable of lambdas) and the last one is the initializer - the first value we want to pass to those lambda functions.

Python for loop in the lambda to create 100 x[i]

In my code I need to create a lambda to realize ax1+~~~~~~zx100, in which a,~~z, are known parameters. I need to put a for loop inside a lambda expression, to realize such function:
x = lambda x: 5*x[0]+20*x[1]+~~~~~~21*x[99]
I wonder, if number of my variables are 1 million, how to realize it? I do not know how to make it happen. Please help, thank you so much!
If you need to pass both the parameters, you could make a lambda to accept both lists, like so:
a = [1,2,3,4,5]
x = [6,7,8,9,0]
sum_of_products = lambda _a,_x: sum(y*z for y, z in zip(_a, _x))
print(sum_of_products(a,x))
80
Alternatively, and preferably you can also just define a normal function for this as well, and achieve the same results.:
def sum_of_products(a, x):
return sum(y*z for y, z in zip(a, x))
Once you've written the function, you can also pass it around just like a lambda, so if you were going to assign it to a variable to begin with, it might be easier to read if you just def your function in the normal way.
a = [1,2,3,4,5]
x = [6,7,8,9,0]
def sum_of_products(_a, _x):
return sum(y*z for y, z in zip(_a, _x))
my_function = sum_of_products
print(my_function(a, x))
80
Try something like this:
lambda x: sum(a * b for a, b in zip(x, [5, 20, ..., 21]))

Convenient way to add lambda functions together?

Let's say I have this function in a python module:
def functionmaker(a,b):
return lambda x: (a*x) + b
Now let's say I am given a list of this format:
ablist = [[a1, b1], [a2, b2]...[an, bn]] of arbitrary length.
My goal is to add up all the resulting functions you get from these a's and b's.
Of course, with the simple example I gave of ax+b, you could algebraically work it out to being (a1 + a2 + ... an)x + (b1 + b2 + ... bn). But let's ignore that for a moment. The function I am actually working with is a contracted gaussian molecular orbital function, and it's not as easy to trick your way out of the problem with algebra.
What I wanted to do was something like this:
function = lambda x: (a1*x) + b1
q = 2
while q < n:
function = lambda x: function(x) + (ablist[q][0]*x) + ablist[q][1])
q += 1
But it seems you can't call a function inside itself like that. I was hoping it would just over-write the old version of "function" with the new one, but I guess not. I guess I probably need to do some kind of recursive way, but maybe there is a nicer way?
By the way I am using python 2.7.
You can do something like this:
>>> def functionmaker(a, b):
... return lambda x: a*x + b
...
>>> def summer_funcs(args):
... return lambda x: sum(functionmaker(*a)(x) for a in args)
...
>>> arguments = [(1,2), (3,4), (5,6)]
>>> summer_funcs(arguments)(1)
21
Or if you prefer:
>>> f = summer_funcs(arguments)
>>> f(1)
21
>>> f(2)
30
If I've correctly understood your question, you could use the built-in sum() function and pass it a generator expression argument to accomplish the goal:
def functionmaker(a, b):
return lambda x: (a*x) + b
ablist = [(1,2), (3,4), (5,6)]
x = 1
total = sum(functionmaker(a, b)(x) for a, b in ablist)
print(total) # -> 21
If you need to compute this quantity for many different values of x, then it might be worthwhile to optimize things for doing that:
functions = [functionmaker(a, b) for a, b in ablist]
sum_of_functions = lambda x: sum(function(x) for function in functions)
for x in (1, 1.5, 2):
print('sum_of_functions(x={:.1f}) -> {:.1f}'.format(x, sum_of_functions(x)))
Output:
sum_of_functions(x=1.0) -> 21.0
sum_of_functions(x=1.5) -> 25.5
sum_of_functions(x=2.0) -> 30.0

Multiple assignment from a function

In Python, is it possible to make multiple assignments in the following manner (or, rather, is there a shorthand):
import random
def random_int():
return random.randint(1, 100)
a, b = # for each variable assign the return values from random_int
Do you want to assign different return values from two different calls to your random function or a single value to two variables generated by a single call to the function.
For the former, use tuple unpacking
t = (2,5)
a,b = t #valid!
def random_int():
return random.randint(1, 100)
#valid: unpack a 2-tuple to a 2-tuple of variables
a, b = random_int(), random_int()
#invalid: tries to unpack an int as a 2-tuple
a, b = random_int()
#valid: you can also use comprehensions
a, b = (random_int() for i in range(2))
For the second, you can chain assignments to assign the same values to multiple variables.
#valid, "normal" way
a = random_int()
b = a
#the same, shorthand
b = a = random_int()
In my test, if you have exactly as many variables as items in your return, then it works.
def algo():
return range(5)
a5 = algo() # works
b1,b2,b3,b4,b5 = algo() # works
c1,c2,c3 = algo() # doesn't work
d1,d2,d3,d4,d5,d6 = algo() # doesn't work.
dictionaries are a good way, or else you can use namedtuples from collections in python, try something like this, you will receive multiple call results in a single variable say a
num = namedtuple("num", "b c d")
a = num(random_int(),random_int(),random_int())
print a.b,a.c,a.d
imports :
from collections import namedtuple
You can return any object, including list and tuple that may be upacked on assignment:
import random
def random_ints():
return random.randint(1, 100), random.randint(1, 100)
a, b = random_ints()
print(b, a)
in fact this a, b is a shortcut for tuple as well, when you do multiple assignment the comma separated list of variables on the left is tuple too and could be written as:
(a, b) = range(2)
My personal favourite would be to convert your function to a generator (provided that it it fits well into your program).
Example:
>>> import random
>>>
>>> def rnd_numbers(how_many=1): # assuming how_many is positive
... for _ in range(how_many): # use xrange() in Python2.x
... yield random.randint(1, 100)
...
>>> x, y, z = rnd_numbers(3)
>>> x
98
>>> y
69
>>> z
16
>>> a,b = rnd_numbers(2)
>>> a
52
>>> b
33

Python Homework - Not making sense

OK, our professor explained (kinda) this problem, but it still doesn't make much sense.
Question: Implement the function knice(f,a,b,k) that will return 1 if for some integer a <= x <= b and some integer n <= k, n applications of f on x will be x, (e.g. f(f(f...(f(x)))) = x) and 0 if not.
What the professor provided was:
def knice(f,a,b,k):
f(f(f(...(f(x)))) = x
for i = a to b:
y = f(i)
if y = i break
for j = z to k:
y = f(y)
if y = i break
Personally, that example makes no sense to me, so looking to see if I can get clarification.
OP EDIT 1/19/2012 3:03pm CST
This is the final function that was figured out with the help of the GTA:
def f(x):
return 2*x-3
def knice(f,a,b,k):
x = a
while x <= b:
n = 1
y = f(x)
if y == x:
return 1
while n <= k:
y = f(y)
n=n+1
if y == x:
return 1
x=x+1
return 0
Ignore his code; you should write whatever you feel comfortable with and work out the kinks later.
You want to work out whether
f(a) = a, or f(f(a)) = a, or ..., or f^n(a) = a, or,
f(a+1) = a+1, or f(f(a+1)) = a+1, or ..., or f^n(a+1) = a+1, or,
...
f(b) = b, or f(f(b)) = b, or ..., or f^n(b) = b.
An obvious algorithm should come to mind immediately: try all these values one-by-one! You will need two (nested) loops, because you are iterating over a rectangle of values. Can you now see what to do?
Yeah, I can see why that might be confusing.
Was f(f(f(...(f(x)))) = x wrapped in triple-double-quotes? That's a function documentation string, sort of like commenting your code. It shouldn't have been stand-alone without something protecting it.
Imagine f was called increment_by_one.
Calling increment_by_one 10 times like that on an x of 2 would give 12. No matter how many times you increment, you never seem to get back 2.
Now imagine f was called multiply_by_one.
Calling multiply_by_one 5 times like that on an x of 3 would give 3. Sweet.
So, some example outputs you can test against (you have to write the functions)
knice(increment_by_one, 1, 3, 5) would return 0.
knice(multiply_by_one, 1, 3, 5) would return 1.
As another hint, indentation is important in python.
Here's a concrete example. Start small, and suppose you called knice(f, a=1, b=2, k=1). For k==1, we don't have to worry about iterating the function. The only values of x to consider are 1 and 2, so knice can return 1 (i.e., True) if f(1)==1 or f(2)==2.
Now suppose you called knice(f, a=1, b=2, k=2). You'll have to check f(f(1)) and f(f(2)) as well.
As k gets bigger, you'll have to call f more. And as the range between a and b gets bigger, you'll have to try more values of x as an argument to f.

Categories