Invalid syntax when trying to map(lambda..) - python

I'm writing a function that loops through a list of numbers, and returns a list of the sums of each number's divisors. I'm trying to trim my function down a little, and learn map(lambda..) at the same time.
def DoWork(nums):
sums = []
for n in nums:
divisors = []
map(lambda i: divisors.append(i) if not (n%i), range(1,n+1))
sums.append(sum(divisors))
return sums
I'm getting a syntax error though, regarding the first , in the map() line.

Typically, we use map() in situations where we have a sequence of things [1] and want to transform it into another sequence -- of the same length -- by applying a function to each element.
You can still use it to find the sum of divisors, but I would argue that it's not the most suitable tool for that.
Instead, let me show you how we can apply map and lambda to your problem by looking at it from a slightly different angle.
First of all, let's rewrite your function to use a list comprehension:
def DoWork(nums):
sums = []
for n in nums:
divisors = [i for i in range(1, n+1) if not n % i]
sums.append(sum(divisors))
return sums
Now observe that the divisors list is unnecessary and can eliminated:
def DoWork(nums):
sums = []
for n in nums:
sums.append(sum(i for i in range(1, n+1) if not n % i))
return sums
Now we can easily transform the entire function into a single call to map():
def DoWork(nums):
return map(lambda n: sum(i for i in range(1, n+1) if not n % i), nums)
[1] map() can also be used to transform multiple sequences -- generally of the same length -- into a single sequence by applying the same function repeatedly across the input sequences.

A python lambda does not work that way. It is should be a simple substitute for a function that takes a value and returns another value. You can not have if conditions floating in it.
updated_range = filter(lambda x: not n%x, range(1,n+1))
map(lambda i: divisors.appens(i), updated_range)
#The above code is a bad style. See below!
Note: Typically, lambdas do not modify an external state. It is considered a bad style to call divisors.append(..)
Improved version #1:
updated_range = filter(lambda x: not n%x, range(1,n+1))
map(lambda i: divisors.append(i), updated_range)
Improved version #2 (fixing the lambda that doesn't modify external state):
updated_range = filter(lambda x: not n%x, range(1,n+1))
divisors = map(lambda i: i, updated_range)
#(lambda i: i) is an identity function: returns whatever we give it.
#Sort of useless, though.
Improved version #3 (More pythonic!):
divisors = [x for x in range(1, n+1) if not n%x]

The syntax error is rising from incomplete 'if not' logic, where an 'else' clause is required.
def DoWork(nums):
sums = []
for n in nums:
divisors = []
map(lambda i: divisors.append(i) if not (n%i) else 1, range(1,n+1))
sums.append(sum(divisors))
return sums
error free execution...
DoWork(range(0,10))
[0, 1, 3, 4, 7, 6, 12, 8, 15, 13]

Related

How to replace your Pythonic For Loops with Map and Filter?

In the spirit of DRY, I was trying to replace and reduce a simple for-loop including if clause unsuccessfully after I read this article by Jonathan Hsu for a The Decent game. The For loop is as follows:
import sys
import math
# Auto-generated code below aims at helping you parse
# the standard input according to the problem statement.
# game loop
while 1:
max = 0
imax = 0
for i in range(8):
mountain_h = int(input()) # represents the height of one mountain, from 9 to 0. Mountain heights are provided from left to right.
if mountain_h > max:
max = mountain_h
imax = i
print(imax)
Based on Jonathan Hsu article I tried to replace for loop by map() and if-clause by filter()as follows:
from functools import reduce
# game loop
while 1:
m = [0,1,2,3,4,5,6,7]
max = 0
imax = 0
u = map(lambda n: mountain_h = int(input()) and print(imax), m)
r = filter(lambda n: mountain_h > max, n)
s = map(lambda n: max = mountain_h and imax = i, r)
# s = reduce(lambda acc, m: acc + n, r)
Any inputs to shed light on the realization of this replacement will be appreciated.
The part you aren't capturing is that your loop is implicitly producing a sequence of calls to input; that's a job for the two-argument form of iter, which will call input until it returns a given value. iter(input, None) can return an infinite number of values, since input() will never return None, but you can short-circuit that using itertools.islice to limit it to 8 calls.
Once you have the inputs (or rather, an iterator that will produce your inputs), you can use enumerate to tag each input with its sequence number and use max (with an appropriate key function) to find the largest value and its index.
from itertools import islice
while 1:
my_inputs = map(int, islice(iter(input, None), 8))
imax, m = max(enumerate(my_inputs), key=lambda x: x[1])
print(imax)
Piece by piece:
iter(input, None) is an infinite stream of calls to input().
islice(..., 8) only yields the first 8 of them.
map(int, ...) calls int on each value. (Warning: this won't catch a ValueError raised by a non-integer input. There's no functional way to deal with the exception.)
enumerate(my_inputs) turns a stream like 3, 7, 2 into (0, 3), (1, 7), (2, 2).
max(..., key=lambda x: x[1]) on the above sequence returns (1, 7), because 7 is the largest second element of all the tuples.
You might also find the use of operator.itemgetter(1) cleaner than an explicit lambda expression as the key argument to max.
A more Pythonic version of the same approach would use a generator function to produce the stream of integer values for max/enumerate to iterate over. It also makes it easier to handle input errors. For example:
def eight_integers():
for _ in range(8):
while True:
try:
x = int(input())
except ValueError:
continue
yield x
imax, m = max(enumerate(eight_integers()), key=lambda x: x[1])
Of course, you could also have eight_integers yield an index and an integer itself, eliminating the need to use enumerate.
So, map is to update elements within a list, filter is to remove elements, and reduce is to collect/aggregate... You are never filtering or mapping data, only gathering input, then compaing against a running max, which would be a use-case for reduce, but still unncessary given Python's max() function.
If you want Pythonic code for this, then it seems you really want a list comprehension, followed by a method that gets the max value & index within that list
values = [int(input()) for i in range(8)]
_max = max(values)
imax = len(values) - 1 - values[::-1].index(_max) # assuming you want the last index
But note that this is effectively replacing one loop with 3 and has nothing to do with DRY principles

Finding two integers that multiply to 20. Can I make this code more "pythonic"?

I did this code that finds two integers in a said list (in this case [2,4,5,1,6,40,-1]) that multiply to twenty. I got a little stuck in the beginning, but adding a function to it solved my problems. I showed this code to a friend of mine who's a programmer and he said I could make this code more "pythonic", but I have no clue how.
Here's the code:
num_list = [2,4,5,1,6,40,-1]
def get_mult_num(given_list):
for i in given_list:
for j in range(i+1, len(given_list)): #for j not to be == i and to be in the list
mult_two_numbers = i * j
if mult_two_numbers == 20:
return i,j
print(get_mult_num(num_list))
I don't necessarily think it is 'unpythonic', you are using standard Python idioms to loop over your data and produce a single result or None. The term Pythonic is nebulous, a subject marred in "I know it when I see it" parameters.
Not that you produced a correct implementation. While i loops over given_numbers, j loops over an integer from i + 2 through to len(given_numbers), mixing values from given_list with indices? For your sample input, you are taking j from the half-open ranges [4, 7), [6, 7), [7, 7) (empty), [3, 7), [8, 7) (empty), [42, 7) (empty) and [1, 7), respectively. That it produces the correct answer at all is luck, not due to correctness; if you give your function the list [2, 10], it'll not find a solution! You want to loop over given_numbers again, limited with slicing, or generate indices starting at the current index of i, but then your outer loop needs to add a enumerate() call too:
for ii, i in enumerate(given_numbers):
for j in given_numbers[ii + 1:]:
# ...
or
for ii, i in enumerate(given_numbers):
for jj in range(ii + 1, len(given_numbers)):
j = given_numbers[jj]
# ...
All this is not nearly as efficient as it can be; the Python standard library offers you the tools to generate your i, j pairs without a nested for loop or slicing or other forms of filtering.
Your double loop should generate combinations of the integer inputs, so use the itertools.combinations() object to generate unique i, j pairs:
from itertools import combinations
def get_mult_num(given_list):
return [(i, j) for i, j in combinations(given_list, 2) if i * j == 20]
This assumes there can be zero or more such solutions, not just a single solution.
If you only ever need the first result or None, you can use the next() function:
def get_mult_num(given_list):
multiplies_to_20 = (
(i, j) for i, j in combinations(given_list, 2)
if i * j == 20)
return next(multiplies_to_20, None)
Next, rather than produce all possible combinations, you may want to invert the problem. If you turn given_list into a set, you can trivially check if the target number 20 can be divided cleanly without remainder by any of your given numbers and where the result of the division is larger and is also an integer in the set of numbers. That gives you an answer in linear time.
You can further limit the search by dividing with numbers smaller than the square root of the target value, because you won't find a larger value to match in your input numbers (given a number n and it's square root s, by definition s * (s + 1) is going to be larger than n).
If we add an argument for the target number to the function and make it a generator function, then you get:
def gen_factors_for(target, numbers):
possible_j = set(numbers)
limit = abs(target) ** 0.5
for i in numbers:
if abs(i) < limit and target % i == 0:
j = target // i
if j in possible_j and abs(j) > abs(i):
yield i, j
This approach is a lot faster than testing all permutations, especially if you need to find all possible factors. Note that I made both functions generators here to even out the comparisons:
>>> import random, operator
>>> from timeit import Timer
>>> def gen_factors_for_division(target, numbers):
... possible_j = set(numbers)
... limit = abs(target) ** 0.5
... for i in numbers:
... if abs(i) < limit and target % i == 0:
... j = target // i
... if j in possible_j and abs(j) > abs(i):
... yield i, j
...
>>> def gen_factors_for_combinations(target, given_list):
... return ((i, j) for i, j in combinations(given_list, 2) if i * j == target)
...
>>> numbers = [random.randint(-10000, 10000) for _ in range(100)]
>>> targets = [operator.mul(*random.sample(set(numbers), 2)) for _ in range(5)]
>>> targets += [t + random.randint(1, 100) for t in targets] # add likely-to-be-unsolvable numbers
>>> for (label, t) in (('first match:', 'next({}, None)'), ('all matches:', 'list({})')):
... print(label)
... for f in (gen_factors_for_division, gen_factors_for_combinations):
... test = t.format('f(t, n)')
... timer = Timer(
... f"[{test} for t in ts]",
... 'from __main__ import targets as ts, numbers as n, f')
... count, total = timer.autorange()
... print(f"{f.__name__:>30}: {total / count * 1000:8.3f}ms")
...
first match:
gen_factors_for_division: 0.219ms
gen_factors_for_combinations: 4.664ms
all matches:
gen_factors_for_division: 0.259ms
gen_factors_for_combinations: 3.326ms
Note that I generate 10 different random targets, to try to avoid a lucky best-case-scenario hit for either approach.
[(i,j) for i in num_list for j in num_list if i<j and i*j==20]
This is my take on it, which uses enumerate:
def get_mult_num(given_list):
return [
item1, item2
for i, item1 in enumerate(given_list)
for item2 in given_list[:i]
if item1*item2 == 20
]
I think your friend may be hinting towards using comprehensions when it makes the code cleaner (sometimes it doesn't).
I can think of using list-comprehension. This also helps to find multiple such-pairs if they exist in the given list.
num_list = [2,4,5,1,6,40,-1]
mult_num = [(num_list[i],num_list[j]) for i in range(len(num_list)) for j in range(i+1, len(num_list)) if num_list[i]*num_list[j] == 20]
print mult_num
Output:
[(4, 5)]
I came up with this. It reverses the approach a little bit, in that it searches in num_list for the required pair partner that the iteration value val would multiply to 20 with. This makes the code easier and needs no imports, even if it's not the most efficient way.
for val in num_list:
if 20 / val in num_list:
print(val, int(20/val))
You could make it more pythonic by using itertools.combinations, instead of nested loops, to find all pairs of numbers. Not always, but often iterating over indices as in for i in range(len(L)): is less pythonic than directly iterating over values as in for v in L:.
Python also allows you to make your function into a generator via the yield keyword so that instead of just returning the first pair that multiplies to 20, you get every pair that does by iterating over the function call.
import itertools
def factors(x, numbers):
""" Generate all pairs in list of numbers that multiply to x.
"""
for a, b in itertools.combinations(numbers, 2):
if a * b == x:
yield (a, b)
numbers = [2, 4, 5, 1, 6, 40, -1]
for pair in factors(20, numbers):
print(pair)

Using recursion to create a list combination

I'm in trouble creating a combination of elements from list.
What i would like to do is to create a recursive function in Python which returns a combination of elements for example list a = [1,2,3,4,5,6,7,8] and a result will be combinations [1,2,3,4],[1,3,4,5],[1,4,5,6],[1,2,4,5] etc. For 8 elements it should return 70 combinations (if i did my math right). Although the best option would be that the combinations don't repeat.
I tried to code it, but what i get is only [1,2,3,4],[1,3,4,5] etc but not combination [1,5,7,8]
I know there is a special function but i'd like to do it recursively. Any suggestions?
nimed = ["A","B","C","D","E","F","G","H"]
def kombinatsioonid(listike,popitav):
if len(listike) < 4:
return
tyhi = []
for c in range(len(listike)):
tyhi.append(listike[c])
listike.pop(popitav)
print(tyhi)
kombinatsioonid(listike,popitav)
kombinatsioonid(nimed,1)
This can be done in this way :
def combination(l,n, mylist=[]):
if not n: print(mylist)
for i in range(len(l)):
mylist.append(l[i])
combination(l[i+1:], n-1, mylist)
mylist.pop()
l = ["A","B","C","D","E","F","G","H"]
n=4
combination(l, n)
For each element x in a, generate all k-1 combinations from the elements right to it, and prepend x to each one. If k==0, simply return one empty combination, thus exiting the recursion:
def combs(a, k):
if k == 0:
return [[]]
r = []
for i, x in enumerate(a):
for c in combs(a[i+1:], k - 1):
r.append([x] + c)
#print '\t' * k, k, 'of', a, '=', r
return r
Uncomment the "print" line to see what's going on.
As a side note, it's better to use English variable and function names, just for the sake of interoperability (your very question being an example).

Rewriting nested if-statements in a more Pythonic fashion

I'm working on a function that, given a sequence, tries to find said sequence within a list and should then return the list item immediately after that sequence terminates.
Currently this code does return the list item immediately after the end of the sequence, however I'm not to happy with having this many nested if-statements and would love to rewrite it but I can't figure out how to go about it as it is quite unlike anything I've ever written in the past and feel a bit out of practice.
def sequence_in_list(seq, lst):
m, n = len(lst), len(seq)
for i in xrange(m):
for j in xrange(n):
if lst[i] == seq[j]:
if lst[i+1] == seq[j+1]:
if lst[i+2] == seq[j+2]:
return lst[i+3]
(My intention is to then extend this function so that if that sequence occurs more than once throughout the list it should return the subsequent item that has happened the most often after the sequence)
I would do this with a generator and slicing:
sequence = [1, 2, 3, 5, 1, 2, 3, 6, 1, 2, 3]
pattern = [1, 2, 3]
def find_item_after_pattern(sequence, pattern):
n = len(pattern)
for index in range(0, len(sequence) - n):
if pattern == sequence[index:index + n]:
yield sequence[index + n]
for item in find_item_after_pattern(sequence, pattern):
print(item)
And you'll get:
5
6
The function isn't too efficient and won't work for infinite sequences, but it's short and generic.
Since you are comparing consecutive indexes, and assuming lst and seq are of the same type, you can use slicing:
def sequence_in_list(seq, lst):
m, n = len(lst), len(seq)
for i in xrange(m):
for j in xrange(n):
if lst[i:i+3] == seq[j:j+3]:
return lst[i+3]
If the sequences are of different kind you should convert to a common type before doing the comparison(e.g. lst[i:i+3] == list(seq[j:j+3]) would work if seq is a string and lst is a list).
Alternatively, if the sequences do not support slicing, you can use the built-in all to check for more conditions:
def sequence_in_list(seq, lst):
m, n = len(lst), len(seq)
for i in xrange(m):
for j in xrange(n):
if all(lst[i+k] == seq[j+k] for k in range(3)):
return lst[i+3]
If you want to extend the check over 10 indices instead of 3, simply change range(3) to range(10).
Side note: your original code would raise an IndexError at some point, since you access list[i+1] where i may be len(list) - 1. The above code doesn't produce any errors, since slicing may produce a slice shorter than the difference of the indices, meainig that seq[j:j+3] can have less than 3 elements. If this is a problem you should adjust the indexes on which you are iterating over.
Last remark: don't use the name list since it shadows a built-in name.
You can combine list comprehension with slicing to make comparing more readable:
n, m = len(lst), len(seq)
[lst[j+3] for i in range(m-2) for j in range(n-2) if seq[i:i+3] == lst[j:j+3]]
Of course there are more efficient ways to do it, but this is simple, short and python styled.

How does reduce function work?

As far as I understand, the reduce function takes a list l and a function f. Then, it calls the function f on first two elements of the list and then repeatedly calls the function f with the next list element and the previous result.
So, I define the following functions:
The following function computes the factorial.
def fact(n):
if n == 0 or n == 1:
return 1
return fact(n-1) * n
def reduce_func(x,y):
return fact(x) * fact(y)
lst = [1, 3, 1]
print reduce(reduce_func, lst)
Now, shouldn't this give me ((1! * 3!) * 1!) = 6? But, instead it gives 720. Why 720? It seems to take the factorial of 6 too. But, I need to understand why.
Can someone explains why this happens and a work-around?
I basically want to compute the product of factorials of all the entries in the list.
The backup plan is to run a loop and compute it. But, I would prefer using reduce.
The other answers are great. I'll simply add an illustrated example that I find pretty good to understand reduce():
>>> reduce(lambda x,y: x+y, [47,11,42,13])
113
will be computed as follows:
(Source) (mirror)
The easiest way to understand reduce() is to look at its pure Python equivalent code:
def myreduce(func, iterable, start=None):
it = iter(iterable)
if start is None:
try:
start = next(it)
except StopIteration:
raise TypeError('reduce() of empty sequence with no initial value')
accum_value = start
for x in iterable:
accum_value = func(accum_value, x)
return accum_value
You can see that it only makes sense for your reduce_func() to apply the factorial to the rightmost argument:
def fact(n):
if n == 0 or n == 1:
return 1
return fact(n-1) * n
def reduce_func(x,y):
return x * fact(y)
lst = [1, 3, 1]
print reduce(reduce_func, lst)
With that small revision, the code produces 6 as you expected :-)
Your function calls fact() on both arguments. You are calculating ((1! * 3!)! * 1!). The workaround is to only call it on only the second argument, and pass reduce() an initial value of 1.
From the Python reduce documentation,
reduce(function, sequence) returns a single value constructed by calling the (binary) function on the first two items of the sequence, then on the result and the next item, and so on.
So, stepping through. It computes reduce_func of the first two elements, reduce_func(1, 3) = 1! * 3! = 6. Then, it computes reduce_func of the result and the next item: reduce_func(6, 1) = 6! * 1! = 720.
You missed that, when the result of the first reduce_func call is passed as input to the second, it's factorialized before the multiplication.
Ok, got it:
I need to map the numbers to their factorials first and then call reduce with multiply operator.
So, this would work:
lst_fact = map(fact, lst)
reduce(operator.mul, lst_fact)
You could also implement factorial using reduce.
def factorial(n):
return(reduce(lambda x,y:x*y,range(n+1)[1:]))
Beyond the trivial examples, here is one where I find reduce to be actually quite useful:
Imagine an iterable of ordered int values, often with some runs of contiguous values, and that we'd like to "summarize" it as a list of tuples representing ranges. (Note also that this iterable could be a generator of a very long sequence --another reason to use reduce and not some operation on an in-memory collection).
from functools import reduce
def rle(a, b):
if a and a[-1][1] == b:
return a[:-1] + [(a[-1][0], b + 1)]
return a + [(b, b + 1)]
reduce(rle, [0, 1, 2, 5, 8, 9], [])
# [(0, 3), (5, 6), (8, 10)]
Notice the use of a proper initial value ([] here) for reduce.
Corner cases handled as well:
reduce(rle, [], [])
# []
reduce(rle, [0], [])
# [(0, 1)]
Well, first of all, your reduce_func doesn't have the structure of a fold; it doesn't match your description of a fold (which is correct).
The structure of a fold is: def foldl(func, start, iter): return func(start, foldl(func, next(iter), iter)
Now, your fact function doesn't operate on two elements - it just calculates factorial.
So, in sum, you're not using a fold, and with that definition of factorial, you don't need to.
If you do want to play around with factorial, check out the y-combinator: http://mvanier.livejournal.com/2897.html
If you want to learn about folds, look at my answer to this question, which demonstrates its use to calculate cumulative fractions: creating cumulative percentage from a dictionary of data
Reduce executes the function in parameter#1 successively through the values provided by the iterator in parameter#2
print '-------------- Example: Reduce(x + y) --------------'
def add(x,y): return x+y
x = 5
y = 10
import functools
tot = functools.reduce(add, range(5, 10))
print 'reduce('+str(x)+','+str(y)+')=' ,tot
def myreduce(a,b):
tot = 0
for i in range(a,b):
tot = tot+i
print i,tot
print 'myreduce('+str(a)+','+str(b)+')=' ,tot
myreduce(x,y)
print '-------------- Example: Reduce(x * y) --------------'
def add(x,y): return x*y
x = 5
y = 10
import functools
tot = functools.reduce(add, range(5, 10))
print 'reduce('+str(x)+','+str(y)+')=' ,tot
def myreduce(a,b):
tot = 1
for i in range(a,b):
tot = tot * i
print i,tot
print 'myreduce('+str(a)+','+str(b)+')=' ,tot
myreduce(x,y)

Categories

Resources