One line code using map, lambda and if condition - python

Using zip and list comprehension, create a new list, L3, that sums the two numbers if the number from L1 is greater than 10 and the number from L2 is less than 5. This can be accomplished in one line of code.
I have tried the codes below. Can anyone explain why python complains?
L1 = [1, 5, 2, 16, 32, 3, 54, 8, 100]
L2 = [1, 3, 10, 2, 42, 2, 3, 4, 3]
L3 = map(lambda (x1,x2): x1 + x2 if(x1 > 10 and x2 < 5) , zip(L1,L2))
L4 = map(lambda x: x[0] + x[1] if(x[0] > 10 and x[1] < 5) , zip(L1,L2))
print(L3)
print(L4)

This is an XY problem. You're using a map when it asks for a list comprehension. It's essentially asking for a filter, not a map.
>>> [x1+x2 for x1, x2 in zip(L1, L2) if x1>10 and x2<5]
[18, 57, 103]
You could do it in a functional style, but it's so ugly. Don't do this.
L3 = list(map(
lambda t: t[0]+t[1],
filter(
lambda t: t[0]>10 and t[1]<5,
zip(L1, L2)
)
))
print(L3) # -> [18, 57, 103]

Have a look here: The lambda function must have a return value and if your if statement is not true, there is none. Therefore, you would have to add an else statement, for example:
L4 = map(lambda x: x[0] + x[1] if (x[0] > 10 and x[1] < 5) else 0, zip(L1,L2))
Results in
[0, 0, 0, 18, 0, 0, 57, 0, 103]
Your first try (L3) does not work because lambda can no longer unpack tuples in python 3.x (see: here)
Please also note that map() returns an object and in order to print its content you would have to call
print(list(map_object))
# in your case:
print(list(L4))
# or set()
Edit based on comment:
if you really want a one-liner and as your first sentence says "using zip and list comprehension" and you want to leave out 0s, then this might work and is easier than map and lambda:
L3 = [x[0]+x[1] for x in zip(L1,L2) if (x[0] > 10 and x[1] < 5)]
Edit 2 if you really, really, really insist on using map and lambda:
L4 = map(lambda x: x[0] + x[1], filter(lambda x: (x[0] > 10 and x[1] < 5), zip(L1,L2)))

Related

Modify function to accept tables as well (Python 3)

I've designed a simple function that looks at an inputted list of numbers, identifies the minimum and maximum values, then substitutes both of them for the midpoint value between them, the function is here:
def mainfunction(list_of_numbers):
smallest_number = list_of_numbers[0]
for a in list_of_numbers:
if a < smallest_number:
smallest_number = a
largest_number = list_of_numbers[0]
for b in list_of_numbers:
if b > largest_number:
largest_number = b
midpoint = (smallest_number + largest_number)/2
final_list = [x if (x != largest_number and x != smallest_number) else midpoint for x in list_of_numbers]
return final_list
print(mainfunction([10, 7, 14, 3, -200, 8, 1, -12, 250]))
Unfortunately, I can't get the function to work on TABLES of numbers, is there an easy way to convert a table of numbers into a list? Any info would be appreciated, cheers!
You can use itertools.chain
from itertools import chain
a = [[3,4], [15,16], [19,20]]
res = list(chain.from_iterable(a))
print(res)
Output:
[3, 4, 15, 16, 19, 20]
with list comprehension
res = [x for lst in a for x in lst]

Filtering with a only one conditional

I'm trying to write a code that uses only lambdas, filter, map and reduce (its a riddle) that accepts a tuple of integers and a tuple of functions, and returns a new tuple of integers who only return one true from the list of functions:
As an example, if the tuple is (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) and the tuple of functions is (lambda x: x > 3, lambda x: x % 2 == 0) I should get a new tuple that looks like [2, 5, 7, 9] because they make only one of the two rules to return True. this is my code so far and I have no idea how to do that...
func = (lambda x: x > 3, lambda x: x % 2 == 0)
data = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
a = lambda func, data: tuple(filter(lambda x: tuple(filter(None, map(lambda f: f(x), func))), data))
print(a(func, data))
This code returns only the integers that apply to both of the terms, but I need to make it to just one.
Here it is:
a = lambda func, data: tuple(filter(lambda d: (sum(map(lambda f: f(d), func)) == 1), data))
Since this really is just a contrived exercise for the sake of it I won't endeavor explaining this horrible expression. But the way to get to it is to first write the code in a clear way with for loops, if blocks etc..., and then one step at a time replace each component with the appropriate map, filter etc...
The one trick I use here is automatic boolean conversion to int: sum(sequence_of_bool) == 1 means exactly one bool is True.
My full test code:
func = (lambda x: x > 3, lambda x: x % 2 == 0)
data = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
a = lambda func, data: tuple(filter(lambda d: (sum(map(lambda f: f(d), func)) == 1), data))
print(a(func, data))
(2, 5, 7, 9)
You can use bool.__xor__ to ensure that only one of the two functions in the func tuple is satisfied:
from functools import reduce
tuple(filter(lambda x: reduce(bool.__xor__, map(lambda f: f(x), func)), data))
This returns:
(2, 5, 7, 9)

Can anybody explain the Logic behind the min([1,2,3,5,6], key=lambda x: abs(x-8))

This is my list: a = [1, 3, 4, 7, 8, 9, 12, 13, 14].
I want to get the number that is closest to 5, this is the solution:
b = min(a, key = lambda x: abs(x-5))
Please explain what is happening in the above line.
The code is using the min builtin function, but with a key parameter. Thus, it does not return the actual minimum element of the list, but the element for which that key function is minimal, i.e. it behaves more like "arg-min" than actually "min".
In the key function (defined as a lambda expression), abs is just the absolute difference, in this case between the parameter x (a number from the list) and 5.
That line is somewhat equivalent to, but much shorter and more readable than, this loop:
a = [1,3,4,7,8,9,12,13,14]
b = min_k = None
for x in a:
k = abs(x-5)
if min_k is None or k < min_k:
b, min_k = x, k
Explanation
min(iterable, key) returns the smallest item in the iterable with respect to the key. So it iterates over the iterable, each time evaluates the key(x) for an element x, and then returns the element for which key(x) was the smallest.
Since key=lambda x=abs(x-5), we thus evaluate the absolute difference between 5, so if x=3, then abs(x-5) is 2, etc. So this will result in the number that is the closest to 5.
Making this an O(log n) algorithm
Given the list is ordered, you can find this in logarithmic time with:
from bisect import bisect_left
def closest(ordered_list, x):
idx = bisect_left(ordered_list, x)
return min(ordered_list[max(idx-1,0):idx+1], key=lambda y: abs(y-x))
For example:
>>> closest(a, -1)
1
>>> closest(a, 0)
1
>>> closest(a, 1)
1
>>> closest(a, 2)
1
>>> closest(a, 3)
3
>>> closest(a, 4)
4
>>> closest(a, 5)
4
>>> closest(a, 6)
7
>>> closest(a, 11)
12
>>> closest(a, 15)
14
abs(x-5)
abs is absolute mathematics function which is equivalent to |x-5|.
example : abs(x-5) when x=6 is 1, and when x=4 is also 1.
lambda x: abs(x-5)
It can be written as
def func(x):
return abs(x-5)
which means for a = [1,3,4,7,8,9,12,13,14]
lambda x: abs(x-5)
will give
[4, 2, 1, 2, 3, 4, 7, 8, 9]
key = lambda x: abs(x-5)
Here the value returned by this lambda function is stored in key variable.
Thus
key = [4, 2, 1, 2, 3, 4, 7, 8, 9]
Lastly min(a, key)
The min function uses key as an iterable to calculate minimum value.
Using the position of minimum value obtained from key it displays the value from iterable a.
Thus for
key = [4(0), 2(1), 1(2), 2(3), 3(4), 4(5), 7(6), 8(7), 9(8)]
minimum value is 1 at position 2 and it displays value at 2 position from iterable a
[1(0), 3(1), 4(2), 7(3), 8(4), 9(5), 12(6), 13(7), 14(8)]
which is 4.
try this
import numpy as np
def return_closest(a, num):
m1 = a[np.searchsorted(a, num)]
m2 = a[np.searchsorted(a, num)-1]
return m2 if abs(num-m1)>abs(num-m2) else m1
a = [1, 3, 4, 7, 8, 9, 12, 13, 14]
return_closest(a, 5)
gives 4
the function np.searchsorted will give you index of the upper limit (7)

Nested List Comprehension with if/else

I have a function that takes a string in a format such as '1,3-5,7,19' and will output the list [1, 3, 4, 5, 7, 19].
However, I was thinking that maybe this was simple enough to do with a nested list comprehension.
My original function is:
result = []
for x in row_range.split(','):
if '-' in x:
for y in range(int(x.split('-')[0]), int(x.split('-')[1]) + 1)):
result.append(y)
else:
result.append(int(x))
I thought the comprehension would be something like:
result = [y for x in row_range.split(',') if '-' in x else int(x) for y in range(int(x.split('-')[0]), int(x.split('-')[1] + 1)]
or even
result = [y for x in row_range.split(',') if '-' in x for y in range(int(x.split('-')[0]), int(x.split('-')[1] + 1) else int(x)]
but these are SyntaxError. Moving the if/else to the front of the comprehension as
result = [y if '-' in x else int(x) for x in row_range.split(',') for y in range(int(x.split('-')[0]), int(x.split('-')[1]) + 1)]
results in an IndexError: list index out of range.
Is this possible? I already have a function that handles it nicely and is more readable but I am simply curious if this can be accomplished in python.
You could define a small helper function:
def foo(x):
x, y = map(int, x.split('-'))
return (x, y + 1)
Now, use a list comprehension with a nested loop.
>>> [y for x in row_range.split(',')
for y in ([int(x)] if '-' not in x else range(*foo(x)))]
[1, 3, 4, 5, 7, 19]
Alternative solution with re.sub() function:
import re
row_range = '1,3-5,7,8,10-14,19' # extended example
cb = lambda r: repr(list(range(int(r.group(1)), int(r.group(2))+1)))[1:-1]
result = [int(i) for i in re.sub(r'(\d+)-(\d+)', cb, row_range).split(',')]
print(result)
The output:
[1, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 19]

Equivalent of Haskell scanl in python

I would like to know if there is a built in function in python for the equivalent Haskell scanl, as reduce is the equivalent of foldl.
Something that does this:
Prelude> scanl (+) 0 [1 ..10]
[0,1,3,6,10,15,21,28,36,45,55]
The question is not about how to implement it, I already have 2 implementations, shown below (however, if you have a more elegant one please feel free to show it here).
First implementation:
# Inefficient, uses reduce multiple times
def scanl(f, base, l):
ls = [l[0:i] for i in range(1, len(l) + 1)]
return [base] + [reduce(f, x, base) for x in ls]
print scanl(operator.add, 0, range(1, 11))
Gives:
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
Second implementation:
# Efficient, using an accumulator
def scanl2(f, base, l):
res = [base]
acc = base
for x in l:
acc = f(acc, x)
res += [acc]
return res
print scanl2(operator.add, 0, range(1, 11))
Gives:
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
Thank you :)
You can use this, if its more elegant:
def scanl(f, base, l):
for x in l:
base = f(base, x)
yield base
Use it like:
import operator
list(scanl(operator.add, 0, range(1,11)))
Python 3.x has itertools.accumulate(iterable, func= operator.add). It is implemented as below. The implementation might give you ideas:
def accumulate(iterable, func=operator.add):
'Return running totals'
# accumulate([1,2,3,4,5]) --> 1 3 6 10 15
# accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
it = iter(iterable)
total = next(it)
yield total
for element in it:
total = func(total, element)
yield total
Starting Python 3.8, and the introduction of assignment expressions (PEP 572) (:= operator), which gives the possibility to name the result of an expression, we can use a list comprehension to replicate a scan left operation:
acc = 0
scanned = [acc := acc + x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 3, 6, 10, 15]
Or in a generic way, given a list, a reducing function and an initialized accumulator:
items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc + x
accumulator = 0
we can scan items from the left and reduce them with f:
scanned = [accumulator := f(accumulator, x) for x in items]
# scanned = [1, 3, 6, 10, 15]
I had a similar need. This version uses the python list comprehension
def scanl(data):
'''
returns list of successive reduced values from the list (see haskell foldl)
'''
return [0] + [sum(data[:(k+1)]) for (k,v) in enumerate(data)]
>>> scanl(range(1,11))
gives:
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
As usual, the Python ecosystem is also overflowing with solutions:
Toolz has an accumulate capable of taking a user-supplied function as an argument. I tested it with lambda expressions.
https://github.com/pytoolz/toolz/blob/master/toolz/itertoolz.py
https://pypi.python.org/pypi/toolz
as does more_itertools
http://more-itertools.readthedocs.io/en/stable/api.html
I did not test the version from more-itertools, but it also can take a user-supplied function.

Categories