Filter function with mapping and lamda - python

I wrote a function trying to compute and map the list, it works fine for this. But when I try to use a filter to filter out any integer values that are less than 5 in map result and return a list, it gives me an error "TypeError: 'NoneType' object is not iterable", can someone help me with this?
def compute(value):
if type(value) == int or type(value) == float:
return value ** 2
elif type(value) == str:
return value[::-1]
def map_compute(my_list):
print(list(map(compute, my_list)))
it works fine until here for the filter option:
def filter_compute(my_list):
number_list = map_compute(my_list)
new_list = list(filter(lambda x: x > 5, number_list))
print(new_list)
filter_compute(['cup', '321', 2, ['x'], 4])
Want I want is that :
Example: function call:
filter_compute(['cup', '321', 2, ['x'], 4])
Expected returned output:
['puc', '123', None, 16]
Another question is that is there any other way, for example just use lambda to do all the above functions?

WARNING
Before anything else, there is an important matter to address: Why are you checking types? It should be avoided as much as possible, particularly in a situation as simple as this one. Is your program purely for educational purposes?
You ask: Another question is that is there any other way, for example just use lambda to do all the above functions? The answer to that is yes, there are other ways, and no, lambda is not a good one.
Code Review
Let's look at your code.
def compute(value):
if type(value) == int or type(value) == float:
return value ** 2
elif type(value) == str:
return value[::-1]
As I mentioned above, the type checking should be avoided. The name of the function and its parameter need improvement, they're generic, nondescript and provide no useful information.
def map_compute(my_list):
print(list(map(compute, my_list)))
print() prints a value to stdout, you probably want return instead. I also strongly discourage the use of map(). That doesn't even matter, however, since you can get rid of this function entirely.
def filter_compute(my_list):
number_list = map_compute(my_list)
new_list = list(filter(lambda x: x > 5, number_list))
print(new_list)
Again, print() -> return. filter(), much like map, is unidiomatic. This function, too, seems unnecessary, although that depends on its intended purpose. Indeed, that code will crash on your example list, since you're comparing an int (5) to strings and a list.
Solution(ish)
Now, here is how I would rewrite your program:
def comp_value(val_in):
if isinstance(val_in, int) or isinstance(val_in, float):
return val_in ** 2
elif isinstance(val_in, str):
return val_in[::-1]
else:
return None
list_1 = ['cup', '321', 2, ['x'], 4]
list_2 = [comp_value(item) for item in list_1]
list_3 = [item for item in list_2 if item > 5]
print(list_3)
The two superfluous functions are replaced with simple list comprehensions. This code still doesn't make much sense and crashes, of course, the important part is how it is written.

Change
def map_compute(my_list):
print(list(map(compute, my_list)))
to
def map_compute(my_list):
return list(map(compute, my_list))
using the print function will return None object from map_compute, which result the number_list var to be None, and will make your exception, caused by the filter function that want to get an iterable item, but will get None

Below (Using inline condition and list comprehension).
It works but it is not very readable and I think you should avoid using this kind of code.
lst = ['cup', '321', 2, ['x'], 4]
new_lst = [x ** 2 if isinstance(x, (int, float)) else x[::-1] if isinstance(x, str) else None for x in lst]
print(new_lst)
output
['puc', '123', 4, None, 16]

Related

How to write Inline/Lambda for appending or not appending to a list

I feel this a noob question but I haven't used lambda functions that much and I couldn't find what I needed online.
I wanted to write a lambda function which takes a series as an input, and returns a list without any 'None's in it
Basically I want a lambda function for the followinf function:
def get_path(x):
toRet = []
for item in x.tolist():
if item is not None:
toret.append(item)
return toRet
Is it possible to get an inline lambda function which does what get_path() does.
Yes I do know that I can do the following:
lambda x: get_path(x)
It solves the problem but I would really love to know how to make an inline function for this.
You don't even need lambda functions here.
toRet = [item for item in x.tolist() if item is not None]
You can also use filter for example, which looks neater
toRet = list(filter(None, x.tolist()))
get_path = lambda x: [ elem for elem in x.tolist() if not elem]
As others have pointed out, you can use filter() but be careful of what function you provide for the filter-check.
If function is None , the identity function is assumed, that is, all elements of iterable that are false are removed.
list(filter(None, something)) won't work correctly because it will filter out False-ish values too, not just None. Example:
>>> mylist = [1, 2, 3, 0, None, '', tuple(), 'last']
>>> list(filter(None, mylist)) # not the expected behaviour
[1, 2, 3, 'last']
OP had an explicit if item is not None check, rather than just if item - so the two are not equivalent. Only None needs to be filtered out. To get the correct filtering behaviour, provide an actual check for the function provided to filter:
>>> list(filter(lambda i: i is not None, mylist))
[1, 2, 3, 0, '', (), 'last']
Replace mylist with x.tolist(). And when you put that into a lambda, it gets messy:
get_path = lambda x: list(filter(lambda i: i is not None, x.tolist()))
Instead of all that, the list comprehension option is better:
get_path = lambda x: [i for i in x.tolist() if i is not None]
with an explicit is not None check.

Reversing ONLY integers in list without slice or reverse()

I am trying to create a program to reverse only the integers in a given list, ignoring floats and strings. In addition, It cannot use any built in functions to do so (reverse() and [::-1]). So far, I have
def intvert(lst):
finallst = []
for i in range(len(lst)-1,-1,-1):
if i == type(int):
finallst.append(i)
elif i != type(int):
continue
return finallst
However, this only produces [] as the output
example: (1,g,2.6,2,3,4,h,dfgs,dsgfgdsg,5) becomes (5,4,3,2,1)
A generator is useful for these kinds of tasks. Note it is better to use isinstance versus type. Also you do not have to explicitly check for non-int types if you aren't going to do anything with them.
def intvert(lst):
for i in range(len(lst)-1, -1, -1):
if isinstance(lst[i], int):
yield lst[i]
list(intvert([1, 2, 3, 4]))
# [4, 3, 2, 1]
list(intvert([1, 'test', 2, ['another test'], 3, 4]))
# [4, 3, 2, 1]
Note that the generator function can be optimised further by converting it into a generator expression:
def intvert(lst):
return (lst[i] for i in range(len(lst)-1, -1, -1) if isinstance(lst[i], int))
The reason nothing's getting appended to your list is because your if condition is erroneous. By typing if i == type(int) you're trying to verify if your counter variable "i" (always an integer) is equal to the type of Python's reserved keyword "int"... which doesn't really make sense.
Here's an if condition that should get this working:
if isinstance(lst[i],int):
finallst.append(lst[i])
You need to index into a value in your list (some indexing exercises here). The "isinstance()" method checks the type of the value at the ith position in "lst" to type int. If True, the value is appended to your list.
Fyi: You also don't need the elif or an else block in this case.
def is_int(val):
if type(val) == int:
return True
else:
return False
lis=[1,"aa",2,"b",3,"c"]
lim=[]
for x in lis:
if is_int(x)==True:
lim.append(x)
k=len(lim)
for i in range(k//2):
a=lim[i]
lim[i]=lim[k-1]
lim[k-1]=a
print(lim)
'''this code does so same logic as yours tell me if you didnt understand.i used
type operater you can use something else.'''
To filter your list you can use list comprehensions:
input_list = [1,2.6,'hi',3,'blah',{},99]
filtered_list = [x for x in input_list if type(x) is int]
>>> [1,3,99]
Then you can reverse that list in whatever way you want according to your homework constraints. Normally you'd call .reverse() on the filtered_list but up to you. If list.reverse() is out, I don't know if list.instert() is allowed, but if it is, then you can do:
reversed_filtered_list = []
for i in filtered_list:
reversed_filtered_list.insert(0, i)

Can I write a lambda function to raise an exception?

Suppose I have the following python list:
my_list = [1, 2,'X', 'Y', 0]
Suppose I want to copy values of this list into a new list as follows:
If it is a digit between 0-9, copy that value into the new list
ElIf it is 'X', copy None into the new list
Else raise an Exception
Can I do it with a lambda function as shown below? If so, how?
new_list = map(lambda(x): something-here-but-what??, my_list)
Why not just write a function that does what you want and put it in the lambda? I don't see a reason to try to make a convoluted one-liner for something that should be more than one line.
my_list = [1, 2,'X', 'Y', 0]
def replace(x):
if x == 'X':
return None
elif type(x) == int and x <= 9 and x >= 0:
return x
else:
raise ValueError('Bad value')
new_list = map(lambda(x): replace(x), my_list[:-2]) # Returns [1, 2, None]
new_list = map(lambda(x): replace(x), my_list) # Raises exception
To back up Brenden's (quite correct) answer...
You can actually do some weird things with Python ternary expressions... but the result is just unbearable. Consider a partial solution:
>>> new_list = map(lambda x: x if isinstance(x, int) and (0 <= x and x <= 9) else ValueError('Bad things happened'), [1, 2, 3, "blah"])
>>> list(new_list)
[1, 2, 3, ValueError('Bad things happened',)]
Not only is that horrid and would probably confuse most Pythonistas (not just the use of an unusual construction, but why would you use this construction?), I don't know quite what to do yet about actually raising the exception right there without redefining the way list() works. (raise only works when it is standing alone.)
So now we have a confusing lambda that conditionally permits a member into the new map construction or includes a ValueError object instead. Yuk.
Much better to abstract this whole idea away behind a function that does, in a very simple way, exactly what you want -- and let the "beautiful code part" be the bit people will normally need to read in the future that goes something like:
new_list = valid_list_to_map(your_list)
Use a conditional expression.
a = list(map(lambda n: n if n in (0,1,2,3,4,5,6,7,8,9) else (None if n == 'X' else 1/0), my_list))
Other exceptions that can be raised:
In the conditional expression replace 1/0 with
{}[n] #KeyError
x #NameError
(_ for _ in ()).throw(Exception('Foo'))) #any kind of exception you want
int('x') #ValueError
To raise an exception you have to use 'try' and 'except' statement and Statements are not allowed in the lambda expression. In the Lambda expression, you can only have expressions so you can't raise the exception in the lambda function.

How to use filter() to remove all strings from a list with strings and numbers?

I have a list for example
list = [1,2,3,'t',4,5,'fgt',6,7,'string']
and I want to use the filter() function to remove all the strings to leave just numbers.
I can do it the normal method, but I cant do it with the filter method...any tips?
so:
list(filter(type(i)==str,a)))
wouldn't work...I tried to use it, but that still doesn't work:
Traceback (most recent call last):
File "<pyshell#11>", line 1, in <module>
list(filter(type(a[-1])==str,a))
TypeError: 'bool' object is not callable
While you could use filter for this, don't. You'd need a lambda function to do it, and it would be both slower and less readable than an equivalent list comprehension or generator expression. Instead, just use the listcomp or genexpr:
old_list = [1,2,3,'t',4,5,'fgt',6,7,'string']
new_list = [x for x in old_list if isinstance(x, (int, float))]
# or to remove str specifically, rather than preserve numeric:
new_list = [x for x in old_list if not isinstance(x, str)]
That's much more straightforward than the filter+lambda equivalent:
new_list = list(filter(lambda x: isinstance(x, (int, float)), old_list))
As noted in COLDSPEED's answer, to be generally accepting of all "number-alikes" you should actually use isinstance with numbers.Number; using (int, float) handles the literal types, but wouldn't handle complex, fractions.Fraction, decimal.Decimal, or third-party numeric types.
If you're looking for a filter, you can shape your lambda to be a bit more elegant.
from numbers import Number
new_list = list(filter(lambda x: isinstance(x, Number), old_list))
numbers.Number is an injected superclass of int and float, and complex. For real types, use numbers.Real instead.
I used this example and it worked for me:
Create a function that takes a list of non-negative integers (positive) and strings and returns a new list with the strings filtered out.
oldlist = [1,2,'a','b']
def filter_list(p):
newlist = list(filter(lambda x: isinstance(x, int), oldlist))
return newlist
It filters out any string and leaves only the 'int' (numbers). Here you have more cases to use instead of 'oldlist' >>>
[1,2,'a','b'] = 1,2
[1,'a','b',0,15] = 1,0,15
[1,7,'af','1','123',123] = 1,7,123
You can check for integers or floats using isinstance:
old_list = [1,2,3,'t',4,5,'fgt',6,7,'string']
new_list = list(filter(lambda x:isinstance(x, int) or isinstance(x, float), old_list))
Output:
[1, 2, 3, 4, 5, 6, 7]

Apply function to one element of a list in Python

I'm looking for a concise and functional style way to apply a function to one element of a tuple and return the new tuple, in Python.
For example, for the following input:
inp = ("hello", "my", "friend")
I would like to be able to get the following output:
out = ("hello", "MY", "friend")
I came up with two solutions which I'm not satisfied with.
One uses a higher-order function.
def apply_at(arr, func, i):
return arr[0:i] + [func(arr[i])] + arr[i+1:]
apply_at(inp, lambda x: x.upper(), 1)
One uses list comprehensions (this one assumes the length of the tuple is known).
[(a,b.upper(),c) for a,b,c in [inp]][0]
Is there a better way? Thanks!
Here is a version that works on any iterable and returns a generator:
>>> inp = ("hello", "my", "friend")
>>> def apply_nth(fn, n, iterable):
... return (fn(x) if i==n else x for (i,x) in enumerate(iterable))
...
>>> tuple(apply_nth(str.upper, 1, inp))
('hello', 'MY', 'friend')
You can extend this so that instead of one position you can give it a list of positions:
>>> def apply_at(fn, pos_lst, iterable):
... pos_lst = set(pos_lst)
... return (fn(x) if i in pos_lst else x for (i,x) in enumerate(iterable))
...
>>> ''.join(apply_at(str.upper, [2,4,6,8], "abcdefghijklmno"))
'abCdEfGhIjklmno'
I commented in support of your first snippet, but here are a couple other ways for the record:
(lambda (a,b,c): [a,b.upper(),c])(inp)
(Won't work in Python 3.x.) And:
[inp[0], inp[1].upper(), inp[2]]
>>> inp = "hello", "my", "friend"
>>> index = 1
>>> inp[:index] + ( str.upper(inp[index]),) + inp[index + 1:]
('hello', 'MY', 'friend')
Seems simple, the only thing you may need to know is that to make a single element tuple, do (elt,)
Maybe some' like this?
>>>inp = ("hello", "my", "friend")
>>>out = tuple([i == 1 and x.upper() or x for (x,i) in zip(t,range(len(t)))])
>>> out
('hello', 'MY', 'friend')
Note: rather than (x,i) in zip(t, range(len(t))) I should have thought of using the enumerate function : (i,x) in enumerate(t)
Making it a bit more general:
Rather than hard-coding the 1, we can place it in a variable.
Also, by using a tuple for that purpose, we can apply the function to elements at multiple indexes.
>>>inp = ("hello", "my", "friend")
>>>ix = (0,2)
>>>out = tuple([i in ix and x.upper() or x for (i, x) in enumerate(t)])
>>> out
('HELLO', 'my', 'FRIEND')
Also, we can "replace" the zip()/enumerate() by map(), in something like
out = tuple(map(lambda x,i : i == 1 and x.upper() or x, inp, range(len(inp)) ) )
Edit: (addressing comment about specifying the function to apply):
Could be something as simple as:
>>> f = str.upper # or whatever function taking a single argument
>>> out = tuple(map(lambda x,i : i == 1 and f(x) or x, inp, range(len(inp)) ) )
Since we're talking about applying any function, we should mention the small caveat with the condition and if_true or if_false construct which is not exactly a substitute for the if/else ternary operator found in other languages. The limitation is that the function cannot return a value which is equivalent to False (None, 0, 0.0, '' for example). A suggestion to avoid this problem, is, with Python 2.5 and up, to use the true if-else ternary operator, as shown in Dave Kirby's answer (note the when_true if condition else when_false syntax of this operator)
I don't understand if you want to apply a certain function to every element in the tuple that passes some test, or if you would like it to apply the function to any element present at a certain index of the tuple. So I have coded both algorithms:
This is the algorithm (coded in Python) that I would use to solve this problem in a functional language like scheme:
This function will identify the element identifiable by id and apply func to it and return a list with that element changed to the output of func. It will do this for every element identifiable as id:
def doSomethingTo(tup, id):
return tuple(doSomethingToHelper(list(tup), id))
def doSomethingToHelper(L, id):
if len(L) == 0:
return L
elif L[0] == id:
return [func(L[0])] + doSomethingToHelper(L[1:], id)
else:
return [L[0]] + doSomethingToHelper(L[1:], id)
This algorithm will find the element at the index of the tuple and apply func to it, and stick it back into its original index in the tuple
def doSomethingAt(tup, i):
return tuple(doSomethingAtHelper(list(tup), i, 0))
def doSomethingAtHelper(L, index, i):
if len(L) == 0:
return L
elif i == index:
return [func(L[0])] + L[1:]
else:
return [L[0]] + doSomethingAtHelper(L[1:], index, i+1)
i also like the answer that Dave Kirby gave. however, as a public service announcement, i'd like to say that this is not a typical use case for tuples -- these are data structures that originated in Python as a means to move data (parameters, arguments) to and from functions... they were not meant for the programmer to use as general array-like data structures in applications -- this is why lists exist. naturally, if you're needing the read-only/immutable feature of tuples, that is a fair argument, but given the OP question, this should've been done with lists instead -- note how there is extra code to either pull the tuple apart and put the resulting one together and/or the need to temporarily convert to a list and back.

Categories