Using math.factorial in a lambda function with reduce() - python

I'm attempting to write a function that calculates the number of unique permutations of a string. For example aaa would return 1 and abc would return 6.
I'm writing the method like this:
(Pseudocode:)
len(string)! / (A!*B!*C!*...)
where A,B,C are the number of occurrences of each unique character. For example, the string 'aaa' would be 3! / 3! = 1, while 'abc' would be 3! / (1! * 1! * 1!) = 6.
My code so far is like this:
def permutations(n):
'''
returns the number of UNIQUE permutations of n
'''
from math import factorial
lst = []
n = str(n)
for l in set(n):
lst.append(n.count(l))
return factorial(len(n)) / reduce(lambda x,y: factorial(x) * factorial(y), lst)
Everything works fine, except when I try to pass a string that has only one unique character, i.e. aaa - I get the wrong answer:
>>> perm('abc')
6
>>> perm('aaa')
2
>>> perm('aaaa')
6
Now, I can tell the problem is in running the lambda function with factorials on a list of length 1. I don't know why, though. Most other lambda functions works on a list of length 1 even if its expecting two elements:
>>> reduce(lambda x,y: x * y, [3])
3
>>> reduce(lambda x,y: x + y, [3])
3
This one doesn't:
>>> reduce(lambda x,y: ord(x) + ord(y), ['a'])
'a'
>>> reduce(lambda x,y: ord(x) + ord(y), ['a','b'])
195
Is there something I should be doing differently? I know I can rewrite the function in many different ways that will circumvent this, (e.g. not using lambda), but I'm looking for why this specifically doesn't work.

See the documentation for reduce(), there is an optional 'initializer' argument that is placed before all other elements in the list so that the behavior for one element lists is consistent, for example, for your ord() lambda you could set initializer to the the character with an ord() of 0:
>>> reduce(lambda x, y: ord(x) + ord(y), ['a'], chr(0))
97

Python's reduce function doesn't always know what the default (initial) value should be. There should be a version that takes an initial value. Supply a sensible initial value and your reduce should work beautifully.
Also, from the comments, you should probably just use factorial on the second argument in your lambda:
reduce(lambda x,y: x * factorial(y), lst, 1)

If you want len(s)! / A!*B!*C! then the use of reduce() won't work, as it will calculate factorial(factorial(A)*factorial(B))*factorial(C). In other words, it really needs the operation to be commutative.
Instead, you'll need to generate the list of factorials, then multiply them together:
import operator
reduce(operator.mul, [factorial(x) for x in lst])

Reduce works by first computing the result for the first two elements in the sequence and then pseudo-recursively follows from there. A list of size 1 is a special case.
I would use a list comprehension here:
prod( [ factorial(val) for val in lst ] )
Good luck!

Related

Problems with 'lambda' expression in python

I have the following problem solving question
Please write a program using generator to print the even numbers
between 0 and n in comma separated form while n is input by console.
Example: If the following n is given as input to the program: 10 Then,
the output of the program should be: 0,2,4,6,8,10.
And below is my answer
n=int(input("enter the number of even numbers needed:"))
eve=''
st=(lambda x:(for i in range(0,x))[(str(i)) if i%2==0 else (",")])(n)
However, I have a problem with the third line that has the lambda
Take full advantage of Python 3's features to create a generator using generator expression syntax, also do the even number stepping with range()'s third paramter.
This would be much briefer:
>>> n = 12
>>>
>>> fn = lambda x: (f"{i}," for i in range(0, x + 1, 2))
>>>
>>> ''.join(list(fn(n)))[:-1] + '.'
'0,2,4,6,8,10,12.'
>>>
>>> fn(10)
<generator object <lambda>.<locals>.<genexpr> at 0x107f67660>
What looks like a tuple comprehension is actually called a "generator expression". Note that in the last line above the interpeter is indicating that the type returned by the lambda is indeed a generator.
Even briefer, you could do it this way:
>>> fn = lambda x: ','.join( (f"{i}" for i in range(0, x + 1, 2)) ) + '.'
>>>
>>> fn(n)
'0,2,4,6,8,10,12.'
>>>
Looks like you might have been on the right track in your question post.
A function that uses the yield keyword also creates a generator. So the other poster is correct in that regard.

Print inside lambda

I'm currently using syntax like this:
print(*list(map(lambda a: [something to do], input())))
It's working fine if the return type is a string but not for a number.
For example. The below script is for finding the square root of three in two different version
print(*list(map(lambda a: int(a)**(1/3), input())))
and
SQRT = lambda a: a**(1/3)
print(SQRT(int(input())))
When I input 9 both return 2.080083823051904 (Which is correct) but when I input 10 the first one return 1.0 0.0 and 2.154434690031884 for the second one.
I wanted to know that is there any way I can print directly from lambda that returns a number without causing a problem addressed above?
You are focusing on the wrong issue here. This is not a problem with printing; removing the print() function from the equation will give you the same results.
You are iterating over the individual characters of the input string, producing the cube root of 1 and 0 respectively when entering '10' into the input prompt, or of 9 when you enter '9':
>>> list(map(lambda a: int(a)**(1/3), '10'))
[1.0, 0.0] # [1 ** (1/3), 0 ** (1/3)]
>>> list(map(lambda a: int(a)**(1/3), '9'))
[2.080083823051904] # [9 ** (1/3)]
input() returns a string object, and strings are iterables; a sequence of the individual characters. For '10' iteration gives you '1' and '0':
>>> list('10') # just iteration, pulling out the separate parts
['1', '0']
Your second code snippets applies int() to the whole input() string, so then you get the square root of 10 and 9:
>>> SQRT(int('10'))
2.154434690031884
>>> SQRT(int('9'))
2.080083823051904
If you wanted the cube root of the input, don't use map():
>>> CBRT = lambda a: a ** (1/3)
>>> CBRT(int('10'))
>>> CBRT(int('10'))
2.154434690031884
Side note: * works on any iterable, including the iterator object that map() produces, so print(*map(...)) works just as well as print(*list(map(...))), but without creating a list object first that then is discarded again.
By using map on the returning string of input(), you are treating the string as a sequence of individual characters, and hence the cube root of 1 and 0 when you input '10'.
You should use the second method you posted, since the all the lambda, map, and unpacking add no value to the problem you're solving.

Tuple in list has less items than the function needs Python 3

I'm kinda new to Programming and Python and I'm self learning before going to uni so please be gentle, I'm a newbie. I hope my english won't have too many grammatical errors.
Basically I had this exercise in a book I'm currently reading to take a list of tuples as a function parameter, then take every item in the each tuple and put it to 2nd power and sum the items up.
My code looks like this and works good if my function call includes the same amount of arguments as the function for loop requires:
def summary(xs):
for x,y,z in xs:
print( x*x + y*y + z*z)
xs =[(2,3,4), (2,-3,4), (1,2,3)]
summary(xs)
However, If I use a list with less tuples than the function definition, I get an error: ValueError : not enough values to unpack(expected 3, got 0):
xs =[(2,3,4), (), (1,2,3)]
I would like to know how to make a function that would accept a tuple I shown before () - with no tuples, and the function would return 0. I have been trying multiple ways how to solve this for 2 days already and googling as well, but it occurs to me I'm either missing something or I'm not aware of a function i could use. Thank you all for the help.
One way is to iterate over the tuple values, this would also be the way to tackle this problem in nearly every programming language:
def summary(xs):
for item in xs:
s = 0
for value in item:
s += value**2
print(s)
Or using a list comprehension:
def summary(xs):
for item in xs:
result = sum([x**2 for x in item])
print(result)
also note that sum([]) will return 0 for an empty iterable.
Well, the issue is that you don't have enough indices in your inner tuple to unpack into three variables. The simplest way to go around it is to manually unpack after checking that you have enough variables, i.e.:
def summary(xs):
for values in xs:
if values and len(values) == 3:
x, y, z = values # or don't unpack, refer to them by index, i.e. v[0], v[1]...
print(x*x + y*y + z*z)
else:
print(0)
Or use a try..except block:
def summary(xs):
for values in xs:
try:
x, y, z = values # or don't unpack, refer to them by index, i.e. v[0], v[1]...
print(x*x + y*y + z*z)
except ValueError: # check for IndexError if not unpacking
print(0)
One way is to use try / except. In the below example, we use a generator and catch occasions when unpacking fails with ValueError and yield 0.
While you are learning, I highly recommend you practice writing functions which return or yield rather than using them to print values.
def summary(xs):
for item in xs:
try:
yield sum(i**2 for i in item)
except ValueError:
yield 0
xs = [(2,3,4), (), (1,2,3)]
res = list(summary(xs))
print(res)
[29, 0, 14]
Or to actually utilise the generator in a lazy fashion:
for i in summary(xs):
print(i)
29
0
14
You should use the "len > 0" condition. This code should work for any list or tuple length :
def summary(xs):
for tup in xs:
prod = [a*a for a in tup if len(tup)>0]
print(sum(prod))
Note that I defined a "prod" list in order to use "sum" so that it is not calculated the hard way. It replaces your "x* x + y* y + z* z" and works for any tuple length.
It often pays to separate your algorithm into functions that just do one thing. In this case a function to sum the squares of a list of values and a function to print them. It is very helpful to keep your variable names meaningful. In this case your xs is a list of lists, so might be better named xss
import math
def sum_of_squares(xs):
return sum(map(math.sqr, xs))
def summary(xss):
for xs in xss:
print sum_of_squares(xs)
xss = [(2,3,4), (), (1,2,3)]
summary(xss)
or
map(print, sum(map(math.sqr, (x for x in xs))))

Three lines to find the greatest product in a string of numbers in Python

Full disclosure: this is for an assignment. Simply getting working code is enough, but doing this in three lines gets me extra credit.
I'm trying to take a 1000-digit string and find the largest product of 5 consecutive digits. You may recognize this as Project Euler's Problem #8.
I've tried a lot of options, but I seem to be stuck. I'm working on figuring out if I can make a lambda statement that will work, but I have no experience with lambda so it's evading me.
Here's what I have so far:
for i in range(1, 996):
max = int(number[i+0]) * int(number[i+1]) * int(number[i+2]) * int(number[i+3]) * int(number[i+4]) if max < int(number[i+0]) * int(number[i+1]) * int(number[i+2]) * int(number[i+3]) * int(number[i+4]) else max = max
return max
That doesn't work and triggers SyntaxError: can't assign to conditional expression.
I don't want outright code, or at least not a complete function, but just a little help understanding how I can move forward.
This isn't legal python:
x = y if z else x = w
This is:
x = y if z else w
So is this:
if z: x = y
By the way, there is a one line solution, that is much shorter and clearer than your three.
= appears twice in your (very long) line. Effectively you have this:
max = something if something else max = max
which Python parses as:
max = (something if something else max) = max
And, indeed, you can't assign to a conditional expression, which is that whole thing in the middle.
You probably didn't intend to have the final = max at the end.
In [15]: def myinput(l,n):
...: for x in l:
...: yield l[x:x+n]
...:
In [16]: max([reduce(lambda a,b:a*b, x) for x in myinput(range(1000),5) if len(x)==5])
Out[16]: 985084775273880L
Like recursive mentioned, there is a simple one-liner solution. It involves using the max function - always bad to name variables after builtins!
In Python 2 it looks something like this:
max(reduce(lambda x, y: x*y, map(int, num[i:i+5])) for i in xrange(996))
In Python 3 reduce was removed, so you have to get it through functools:
from functools import reduce
max(reduce(lambda x, y: x*y, map(int, num[i:i+5])) for i in range(996))
Look into:
the built-in max function to find the greatest number in a sequence,
the built-in map function to apply a function to all elements in a list,
the built-in reduce function to obtain a single object as a result of applying a function that returns a single object repeatedly to two elements in a list,
lambda definitions to be able to define function objects that you can pass to map() and reduce(),
and list comprehensions (and generators, which are very similar) to compose the above functions in a one-liner.

For loops (novice)

I recently started learning Python, and the concept of for loops is still a little confusing for me. I understand that it generally follows the format for x in y, where y is just some list.
The for-each loop for (int n: someArray)
becomes for n in someArray,
And the for loop for (i = 0; i < 9; i-=2) can be represented by for i in range(0, 9, -2)
Suppose instead of a constant increment, I wanted i*=2, or even i*=i. Is this possible, or would I have to use a while loop instead?
As you say, a for loop iterates through the elements of a list. The list can contain anything you like, so you can construct a list beforehand that contains each step.
A for loop can also iterate over a "generator", which is a small piece of code instead of an actual list. In Python, range() is actually a generator (in Python 2.x though, range() returned a list while xrange() was the generator).
For example:
def doubler(x):
while True:
yield x
x *= 2
for i in doubler(1):
print i
The above for loop will print
1
2
4
8
and so on, until you press Ctrl+C.
You can use a generator expression to do this efficiently and with little excess code:
for i in (2**x for x in range(10)): #In Python 2.x, use `xrange()`.
...
Generator expressions work just like defining a manual generator (as in Greg Hewgill's answer), with a syntax similar to a list comprehension. They are evaluated lazily - meaning that they don't generate a list at the start of the operation, which can cause much better performance on large iterables.
So this generator works by waiting until it is asked for a value, then asking range(10) for a value, doubling that value, and passing it back to the for loop. It does this repeatedly until the range() generator yields no more values.
Bear in mind that the 'list' part of the Python can be any iterable sequence.
Examples:
A string:
for c in 'abcdefg':
# deal with the string on a character by character basis...
A file:
with open('somefile','r') as f:
for line in f:
# deal with the file line by line
A dictionary:
d={1:'one',2:'two',3:'three'}
for key, value in d.items():
# deal with the key:value pairs from a dict
A slice of a list:
l=range(100)
for e in l[10:20:2]:
# ever other element between 10 and 20 in l
etc etc etc etc
So it really is a lot deeper than 'just some list'
As others have stated, just set the iterable to be what you want it to be for your example questions:
for e in (i*i for i in range(10)):
# the squares of the sequence 0-9...
l=[1,5,10,15]
for i in (i*2 for i in l):
# the list l as a sequence * 2...
You will want to use list comprehensions for this
print [x**2 for x in xrange(10)] # X to the 2nd power.
and
print [x**x for x in xrange(10)] # X to the Xth power.
The list comprehension syntax is a follows:
[EXPRESSION for VARIABLE in ITERABLE if CONDITION]
Under the hood, it acts similar to the map and filter function:
def f(VARIABLE): return EXPRESSION
def c(VARIABLE): return CONDITION
filter(c, map(f, ITERABLE))
Example given:
def square(x): return x**2
print map(square, xrange(10))
and
def hypercube(x): return x**x
print map(hypercube, xrange(10))
Which can be used as alternative approach if you don't like list comprehensions.
You could as well use a for loop, but that would step away from being Python idiomatic...
Just for an alternative, how about generalizing the iterate/increment operation to a lambda function so you can do something like this:
for i in seq(1, 9, lambda x: x*2):
print i
...
1
2
4
8
Where seq is defined below:
#!/bin/python
from timeit import timeit
def seq(a, b, f):
x = a;
while x < b:
yield x
x = f(x)
def testSeq():
l = tuple(seq(1, 100000000, lambda x: x*2))
#print l
def testGen():
l = tuple((2**x for x in range(27)))
#print l
testSeq();
testGen();
print "seq", timeit('testSeq()', 'from __main__ import testSeq', number = 1000000)
print "gen", timeit('testGen()', 'from __main__ import testGen', number = 1000000)
The difference in performance isn't that much:
seq 7.98655080795
gen 6.19856786728
[EDIT]
To support reverse iteration and with a default argument...
def seq(a, b, f = None):
x = a;
if b > a:
if f == None:
f = lambda x: x+1
while x < b:
yield x
x = f(x)
else:
if f == None:
f = lambda x: x-1
while x > b:
yield x
x = f(x)
for i in seq(8, 0, lambda x: x/2):
print i
Note: This behaves differently to range/xrange in which the direction </> test is chosen by the iterator sign, rather than the difference between start and end values.

Categories

Resources