list comprehension (or one-liners) with if statement - python

In python, for iterables many one-line iteration commands can be constructed.
For some of such iterations if-statements are required.
Sometimes the order of if-statement and for-statement is important.
Suppose I want to find sum of odd numbers between 0 and 10:
>>> sum(i if not i%2==0 for i in range(10))
SyntaxError: invalid syntax
>>> sum(i for i in range(10) if not i%2==0)
25
Those one-liners are inherently not very comprehensible, however I don't really understand why the if-statement has to come after the for-statement. wouldn't it more fit common sense to use the previous i if not i%2==0 for i in range(10)?

In a generator expression (or a list comprehension), the statements should be listed as if you were nesting them.
Your sum() expression can be nested as:
for i in range(10):
if not i%2 == 0:
# do something with i
You cannot change that ordering, the following would not make sense since i is not defined:
if not i%2 == 0:
for i in range(10):
# do something with i

In your first example, you have an unconditional loop with an if-expression as the list comprehension value. The correct syntax would be:
(a if b else c for i in iterable)
\___________/
|
actual expression
a if b else c is equivalent to a if b evaluates to true, and c otherwise.
In your second example, you have a conditional list comprehension. You are basically skipping over values from the iterable; or more precisely, you are specifying which are taken:
(a for i in iterable if <condition>)
In case of a sum, you can rewrite your list comprehension using the first syntax, as zero is a null element for the addition (and as such the sum):
sum(i if i % 2 != 0 else 0 for i in range(10))

Related

what's the difference between filter and comprehention with if?

def anagramwordchecker(z,w):
if sorted([x for x in w])==sorted([x for x in z]):return True
return False
def anagramlistchecker(l,w):
d={}
for x in w:
d.update({x:w.count(x)})
l=list(filter(lambda x:anagramwordchecker(x,w),l))
return l
print(anagramlistchecker(['bcda', 'abce', 'cbda', 'cbea', 'adcb'],'abcd'))
trying to check which words are anagram.
using both of this it will print the same:
l=[x for x in l if anagramwordchecker(x,w)]
l=list(filter(lambda x:anagramwordchecker(x,w),l))
and it will be:
['bcda', 'cbda', 'adcb']
then what's the difference? any advantage using filter? cause comprehension is easier.
If you print the results of the following example, you will know which one is faster (Comments are results I got).
timeit.Timer('''[x for x in range(100) if x % 2 == 0]''' ).timeit(number=100000)
timeit.Timer('''list(filter(lambda x: x % 2 == 0, range(100)))''').timeit(number=100000)
# 0.3664856200000486
# 0.6642515319999802
So in your case, list comprehension would be faster. But let's see the following example.
timeit.Timer('''[x for x in range(100) if x % 2 == 0]''' ).timeit(number=100000)
timeit.Timer('''(x for x in range(100) if x % 2 == 0)''' ).timeit(number=100000)
timeit.Timer('''filter(lambda x: x % 2 == 0, range(100))''').timeit(number=100000)
# 0.5541256509999357
# 0.024836917000016
# 0.017953075000036733
The results show that casting an iterable to list takes much time and filter is faster than generator expression. So if your result does not really have to be a list, returning an iterable in a timely manner would be better.
As stated in here,
Note that filter(function, iterable) is equivalent to the generator expression (item for item in iterable if function(item)) if function is not None and (item for item in iterable if item) if function is None.
But list comprehension can do much more than simply filtering. If filter is given to the interpreter, it will knows it is a filter function. However, if a list comprehension is given to the interpreter, the interpreter does not know what it really is. After taking some time interpreting the list comprehension to something like a function, it would be a filter or filterfalse function in the end. Or, something else completely different.
filter with not condition can do what filterfalse does. But filterfalse is still there. Why? not operator does not need to be applied.
There is no magic. Human-friendly 1-for-many grammars are based on encapsulation. For them to be machine-executable binaries, they need to be decapsulated back and it takes time.
Go with a specific solution if it is enough than taking a more general solutions. Not only in coding, general solutions are usually for convenience, not for best results.

Stacking inline For plus If else

I wrote a line that should sum up all numbers of a string except 0. On a zero it should add 5.
Whats wrong with
s="123450"
o=sum([int(x) for x in s if int(x) != 0 else 5])
it gives syntax error, but
s="123450"
o=sum([int(x) for x in s if int(x) != 0])
works fine.
if at the end of a list comprehension is used as a filter. It can only cause values to be dropped, not replace them with other things. To map values, you have to move the if/else earlier in the comprehension:
o=sum([int(x) if int(x) != 0 else 5 for x in s])
The correct syntax for the first case if -
s="123450"
o=sum([int(x) if int(x) != 0 else 5 for x in s ])
print(o)
OUTPUT :
20
There are many good answers already available on StackOverflow over if-else in list comprehension. Check these -
if else in a list comprehension
List comprehension with if statement
if/else in a list comprehension
More of an aside really (as the general issue of how you are using if inside list comprehensions is well covered in other answers), in this particular case you could do the following:
o = sum(int(x) or 5 for x in s)
This works because or will use the first value if it is "true" (which in the case of integers means non-zero) or the second value if the first is "false" (here, 0).
The other difference here is that I've used a generator expression instead of a list comprehension -- there is no need to build a list just in order to sum the values.

List Comprehension Python Prime numbers

I came across a solution on Stack Overflow to generate prime numbers using list comprehension. But was unable to understand what does the inner for loop do.
I have tried something like
[x for x in range(5,20) for y in range(2,int(x/2)+1) if any(x%y == 0)]
It throws an error: 'bool' object is not iterable
I know that my syntax is wrong but logically for prime numbers we have a for loop followed by a for loop and then a if condition to calculate remainder(x%y).
But the answer on Stack Overflow is
[x for x in range(2, 20) if all(x % y != 0 for y in range(2, x))]
I understood the reason why all is used, but I am unable to get how the condition inside all() is working as ideally for should be following if so that range(2,x) is iterated and y gets values which are in turn used for computing(x%y). How can y be used even before it is has been assigned a value.
That is just the wonderful thing about list comprehension if it can work normally like the for loop, people wont create it because the for loop is more readable and understandable.
You may find out that the result of list comprehension is always a list, meanwhile the result of for loop would always many single values and these single values is a part of iterable
[x +1 for x in range(1,5)]
[2, 3, 4, 5]
for x in range (1,10): print(x+1)
2
3
4
5
You can simply understand that the loop comprehension already have the list of values, then they just simply feed orderly to the condition value by value. Like this:
[1+1 , 2+1 , 3+1 , 4+1]
Your code is wrong because you inherit too much from the ordinary for loop. Your code written in for loop would be like this:
for x in range(5,20):
for y in range(2,int(x/2)+1):
if any(x%y == 0):
print(x)
And the result would obviously:
TypeError: 'bool' object is not iterable
because any requires an iterable such as a generator expression or a **list** as mentioned above by #meowgoesthedog . Coincidentally, list is just all about list comprehension. However, you need comprehend it in order to utilize the list comprehension well. It sometimes happens to me too, in your case, the for y in range(2,int(x/2)+1) works as a normal for loop.
This is the syntax of list comprehension.
In side the condition if which is optional predicate. We can create another list comprehension by following the rules with x%y==0 is output expression and a variable y representing members of the input sequence range(2,int(x/2)+1)
all() and any() works on itterable objects. For example all([True, True, False, True]) returns False. You cant use any(True) (like in your example: any(x%y == 0))
This statement [x for x in range(2, 20) if all(x % y != 0 for y in range(2, x))] can be translated to this code:
res = []
for x in range(2, 20):
temporary_list = (x%y != 0 for y in range(2,x))
if all(temporary_list):
res.append(x)
Ps. I saw in comments that you are not sure how y is declared. In python, there are more great structures than list of comprehension. One of them is generator of comprehension - I believe it is used in this case.
The syntax all and any work on iterable objects (list, sets, etc). Therefore you get an error when you apply it on boolean - x%y==0.
You can use any in the following manner -
[x for x in range(5,20) if not any([x % y == 0 for y in range(2, int(x/2)+1)])]
or -
[x for x in range(2, 20) if not any(x % y == 0 for y in range(2, int(x/2)+1))]
As any and all complement each other.

How can I add nothing to the list in list comprehension?

I am writing a list comprehension in Python:
[2 * x if x > 2 else add_nothing_to_list for x in some_list]
I need the "add_nothing_to_list" part (the else part of the logic) to literally be nothing.
Does Python have a way to do this? In particular, is there a way to say a.append(nothing) which would leave a unchanged. This can be a useful feature to write generalized code.
Just move the condition to the last
[2 * x for x in some_list if x > 2]
Quoting the List Comprehension documentation,
A list comprehension consists of brackets containing an expression followed by a for clause, then zero or more for or if clauses. The result will be a new list resulting from evaluating the expression in the context of the for and if clauses which follow it.
In this case, the expression is 2 * x and then a for statement, for x in some_list, followed by an if statement, if x > 2.
This comprehension can be understood, like this
result = []
for x in some_list:
if x > 2:
result.append(x)

Removing Redundancies/Condensing Code

Both code examples below are old examples of a problem I have where I am iterating through a list of numbers to find numbers that fit a list of conditions but couldn't find a neat way to express it except for:
condition1 and condition2 and condition3 and condition4 and condition5
Two examples of the above:
if str(x).count("2")==0 and str(x).count("4")==0 and str(x).count("6")==0 and str(x).count("8")==0 and str(x).count("0")==0:
if x % 11==0 and x % 12 ==0 and x % 13 ==0 and x%14==0 and x %15 == 0 and x%16==0 and x%17==0 and x%18==0 and x%19==0 and x%20==0:
Is there a simple, more neat way of doing this?
My first retry was to store the conditions in a list like below:
list=["2","4","6","8","0"]
for element in list:
#call the function on all elements of the list
list=[11,12,13,14,15,16,17,18,19,20]
for element in list:
#call the function on all elements of the list
but I was hoping for an even neater/simpler way.
You can use a generator expression like this
def f(n):
return x%n
if all(f(element) for element in lst):
...
If the function/calculation is not too complex, you can just put it inline
if all(x % element for element in lst):
...
The built in function all can simplify this, if you can express your conditions in a generator expression:
result = all(x % n == 0 for n in xrange(11, 21))
It returns a boolean result indicating whether or not all the elements of its iterable argument are True, short-circuiting to end evaluation as soon as one is False.
This is the second question I've seen in the last hour or so for which all is the answer - must be something in the air.

Categories