Compressing function within comprehension - python

I am taking l=['1','2','3','rt4','rt5'] as input and I am converting it into l=[1,2,3,'rt4','rt5'] with the following code:
def RepresentsInt(s):
try:
int(s)
return True
except ValueError:
return False
l=['1','2','3','rt4','rt5']
l=[int(l[i]) if RepresentsInt(l[i]) else l[i] for i in range(0,len(l))]
Can I improve above code using a comprehension?

You could change your RepresentsInt function to actually return the integer (if possible) which would make this much easier:
def RepresentsInt(s):
try:
return int(s)
except ValueError:
return s
Then the code to transform the list could be written as (using a for item in l loop is probably better than iterating over the indices):
>>> l = ['1','2','3','rt4','rt5']
>>> [RepresentsInt(item) for item in l]
[1, 2, 3, 'rt4', 'rt5']
Or if you want that as a reusable pattern you still need a helper function (I chose a decorator-like approach here) because you can't use try and/or excepts in comprehensions:
def try_to_apply_func(func, exception):
def newfunc(value):
try:
return func(value)
except exception:
return value
return newfunc
>>> to_int_if_possible = try_to_apply_func(int, ValueError)
>>> [to_int_if_possible(item) for item in l]
[1, 2, 3, 'rt4', 'rt5']
>>> to_float_if_possible = try_to_apply_func(float, ValueError)
>>> [to_float_if_possible(item) for item in l]
[1.0, 2.0, 3.0, 'rt4', 'rt5']

It's really unclear what you want, but maybe something like :
>>> l=['1','2','3','rt4','rt5']
>>> l=[int(i) if i.isdigit() else i for i in l]
>>> l
[1, 2, 3, 'rt4', 'rt5']

You can use the following code to get your desired result.
l = ['1','2','3','rt4','rt5']
l = [int(each) if each.isdigit() else each for each in l]
print l

I don't believe (though I could ultimately be wrong) that there is a cleaner and neater way to achieve this. One solution would be to create an integer parsing lambda expression, such as the following, but I think your current solution is much neater and more robust.
>>> l = ['1','2','3','rt4','rt5']
>>> l = list(map(lambda s : (s.isdigit() or (s[0] == '-' and s[1:].isdigit())) and int(s) or s, l))
>>> l
[1, 2, 3, 'rt4', 'rt5']
This won't correctly catch strings such as '1.0' or ' 1', but it should just about do what you want in two lines.

Related

Filter function with mapping and lamda

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]

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.

Split string to various data types

I would like to convert the following string:
s = '1|2|a|b'
to
[1, 2, 'a', 'b']
Is it possible to do the conversion in one line?
Is it possible to do the conversion in one line?
YES, It is possible. But how?
Algorithm for the approach
Split the string into its constituent parts using str.split. The output of this is
>>> s = '1|2|a|b'
>>> s.split('|')
['1', '2', 'a', 'b']
Now we have got half the problem. Next we need to loop through the split string and then check if each of them is a string or an int. For this we use
A list comprehension, which is for the looping part
str.isdigit for finding if the element is an int or a str.
The list comprehension can be easily written as [i for i in s.split('|')]. But how do we add an if clause there? This is covered in One-line list comprehension: if-else variants. Now that we know which all elements are int and which are not, we can easily call the builtin int on it.
Hence the final code will look like
[int(i) if i.isdigit() else i for i in s.split('|')]
Now for a small demo,
>>> s = '1|2|a|b'
>>> [int(i) if i.isdigit() else i for i in s.split('|')]
[1, 2, 'a', 'b']
As we can see, the output is as expected.
Note that this approach is not suitable if there are many types to be converted.
You cannot do it for negative numbers or lots of mixed types in one line but you could use a function that would work for multiple types using ast.literal_eval:
from ast import literal_eval
def f(s, delim):
for ele in s.split(delim):
try:
yield literal_eval(ele)
except ValueError:
yield ele
s = '1|-2|a|b|3.4'
print(list(f(s,"|")))
[1, -2, 'a', 'b', 3.4]
Another way, is using map built-in method:
>>> s='1|2|a|b'
>>> l = map(lambda x: int(x) if x.isdigit() else x, s.split('|'))
>>> l
[1, 2, 'a', 'b']
If Python3, then:
>>> s='1|2|a|b'
>>> l = list(map(lambda x: int(x) if x.isdigit() else x, s.split('|')))
>>> l
[1, 2, 'a', 'b']
Since map in Python3 would give a generator, so you must convert it to list
It is possible to do arbitrarily many or complex conversions "in a single line" if you're allowed a helper function. Python does not natively have a "convert this string to the type that it should represent" function, because what it "should" represent is vague and may change from application to application.
def convert(input):
converters = [int, float, json.loads]
for converter in converters:
try:
return converter(input)
except (TypeError, ValueError):
pass
# here we assume if all converters failed, it's just a string
return input
s = "1|2.3|a|[4,5]"
result = [convert(x) for x in s.split("|")]
If you have all kinds of data types(more than str and int), I believe this does the job.
s = '1|2|a|b|[1, 2, 3]|(1, 2, 3)'
print [eval(x) if not x.isalpha() else x for x in s.split("|")]
# [1, 2, 'a', 'b', [1, 2, 3], (1, 2, 3)]
This fails if there exists elements such as "b1"

How to print a list in the format of a set. In Python3

I need to print the elements of a list in the form of a set. So if I have the list [1,2,3] the print statement needs to return {1,2,3}.
Here it is
s=[1,2,3]
print("{"+str(s)[1:-1]+"}")
Here's one way (where x is your list):
>>> print("{" + ', '.join(str(item) for item in x) + "}")
{1, 2, 3}
If you want a set, why not turn the list into a set? With x being your list:
>>> x=[1,2,3]
>>> x_set=set(x)
>>> print(x_set)
{1, 2, 3}
It's noteworthy that sets are not ordered and cannot contain duplicates. (But then again what is the benefit of printing something to look 'like' a set, but is not?)
>>> x=[1,2,3,3,4]
>>> x_set=set(x)
>>> print(x_set)
{1, 2, 3, 4}
If you want specific formatting, you will need to write that yourself.
Here is a subclass of set that implements that format:
class Myset(set):
def __str__(self):
return '{'+','.join(repr(e) for e in sorted(self))+'}'
print Myset([1,2,3])
# {1,2,3}
Or, just use Python 3x:
>>> print(set([1,2,3]))
{1, 2, 3}
The order may be different, however, without the sorted that I used above...
("{{{}}}".format(",".join(map(str,l))))
{1,2,3}
Extend the class from list and override __repr__ method
class List(list):
def __repr__(self):
return "{%s}"%(",".join([str(each) for each in self]))
l = List()
l.append(1)
l.append(2)
print l
{1,2}

How do I do what strtok() does in C, in Python?

I am learning Python and trying to figure out an efficient way to tokenize a string of numbers separated by commas into a list. Well formed cases work as I expect, but less well formed cases not so much.
If I have this:
A = '1,2,3,4'
B = [int(x) for x in A.split(',')]
B results in [1, 2, 3, 4]
which is what I expect, but if the string is something more like
A = '1,,2,3,4,'
if I'm using the same list comprehension expression for B as above, I get an exception. I think I understand why (because some of the "x" string values are not integers), but I'm thinking that there would be a way to parse this still quite elegantly such that tokenization of the string a works a bit more directly like strtok(A,",\n\t") would have done when called iteratively in C.
To be clear what I am asking; I am looking for an elegant/efficient/typical way in Python to have all of the following example cases of strings:
A='1,,2,3,\n,4,\n'
A='1,2,3,4'
A=',1,2,3,4,\t\n'
A='\n\t,1,2,3,,4\n'
return with the same list of:
B=[1,2,3,4]
via some sort of compact expression.
How about this:
A = '1, 2,,3,4 '
B = [int(x) for x in A.split(',') if x.strip()]
x.strip() trims whitespace from the string, which will make it empty if the string is all whitespace. An empty string is "false" in a boolean context, so it's filtered by the if part of the list comprehension.
Generally, I try to avoid regular expressions, but if you want to split on a bunch of different things, they work. Try this:
import re
result = [int(x) for x in filter(None, re.split('[,\n,\t]', A))]
Mmm, functional goodness (with a bit of generator expression thrown in):
a = "1,2,,3,4,"
print map(int, filter(None, (i.strip() for i in a.split(','))))
For full functional joy:
import string
a = "1,2,,3,4,"
print map(int, filter(None, map(string.strip, a.split(','))))
For the sake of completeness, I will answer this seven year old question:
The C program that uses strtok:
int main()
{
char myLine[]="This is;a-line,with pieces";
char *p;
for(p=strtok(myLine, " ;-,"); p != NULL; p=strtok(NULL, " ;-,"))
{
printf("piece=%s\n", p);
}
}
can be accomplished in python with re.split as:
import re
myLine="This is;a-line,with pieces"
for p in re.split("[ ;\-,]",myLine):
print("piece="+p)
This will work, and never raise an exception, if all the numbers are ints. The isdigit() call is false if there's a decimal point in the string.
>>> nums = ['1,,2,3,\n,4\n', '1,2,3,4', ',1,2,3,4,\t\n', '\n\t,1,2,3,,4\n']
>>> for n in nums:
... [ int(i.strip()) for i in n if i.strip() and i.strip().isdigit() ]
...
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
How about this?
>>> a = "1,2,,3,4,"
>>> map(int,filter(None,a.split(",")))
[1, 2, 3, 4]
filter will remove all false values (i.e. empty strings), which are then mapped to int.
EDIT: Just tested this against the above posted versions, and it seems to be significantly faster, 15% or so compared to the strip() one and more than twice as fast as the isdigit() one
Why accept inferior substitutes that cannot segfault your interpreter? With ctypes you can just call the real thing! :-)
# strtok in Python
from ctypes import c_char_p, cdll
try: libc = cdll.LoadLibrary('libc.so.6')
except WindowsError:
libc = cdll.LoadLibrary('msvcrt.dll')
libc.strtok.restype = c_char_p
dat = c_char_p("1,,2,3,4")
sep = c_char_p(",\n\t")
result = [libc.strtok(dat, sep)] + list(iter(lambda: libc.strtok(None, sep), None))
print(result)
Why not just wrap in a try except block which catches anything not an integer?
I was desperately in need of strtok equivalent in Python. So I developed a simple one by my own
def strtok(val,delim):
token_list=[]
token_list.append(val)
for key in delim:
nList=[]
for token in token_list:
subTokens = [ x for x in token.split(key) if x.strip()]
nList= nList + subTokens
token_list = nList
return token_list
I'd guess regular expressions are the way to go: http://docs.python.org/library/re.html

Categories