-1

I found some strange behavior in python. Possibly my logic is not correct.

1 and 2 and 3 in range(5)
Expected: True
Outcome: True

2 and 1 and 99 in range(5)
Expected: False
Outcome False

2 and 1 and 0 in range(5)
Expected: True
Outcome: True

Now the tricky one:

0 and 1 and 2 in range(5)
Expected: True
Outcome: 0

I am sure there is someone who makes me find my logical error.

SDahm
  • 474
  • 2
  • 9
  • 21
  • 1
    `1 and 2 and 3 in range(5)` ==> `(1 is truthy) and (2 is truthy) and (3 in range(5) is truthy)`. Also read about short-circuiting of boolean operators. – Pranav Hosangadi Nov 03 '20 at 15:05
  • Does this answer your question? [How to test multiple variables against a value?](https://stackoverflow.com/questions/15112125/how-to-test-multiple-variables-against-a-value) – OneCricketeer Nov 03 '20 at 16:10
  • Actually it is: How to test multiple variables against multiple values / a list of values? – SDahm Nov 04 '20 at 08:53

1 Answers1

1

In each expression, only the last number is checked against the range. The previous ones are evaluated "as is". In python, the expression if i: evaluates to True if i is not 0.

The value returned from the expressions (boolean or int) depends on what the conditions are. If you leave just 1 and 2 for example, the result will be the last int. However, since you have the v in range(n) expression, which returns True or False, the result is cast into a boolean value.

Now, due to short-circuit evaluation, in the last case, only the zero gets evaluated. So the result is not cast into a boolean and 0 is returned.

Edit: After reading the comments, it becomes clear that you want to check if k number exist in range(n). For that, you cannot use the simple expressions you've shown. You need to check if every individual value exists in the range. One - inefficient - approach would be this

if all([v in range(n) for v in values]):
    print("All values exist in the range")

Edit 2 (by @Pranav Hosangadi)

Side note: Since the all() function takes generator expressions, you can avoid the list-comprehension altogether. When you do this, the generator expression will only calculate as many items as needed for the all() to short-circuit. On the other hand, the list-comprehension approach will calculate all elements in the list, and then run all() on that list. Here's a simple example:

l = [100] * 10000
l[-1] = 0

def f1(): # Using generator
    return all(li == 0 for li in l)

def f2(): # Using list comp
    return all([li == 0 for li in l])

Now for the generator-expression approach, all() needs to calculate only the first element to know that it will short-circuit to False. However, the list-comprehension approach calculates all elements of the list first. All but the last element of this list are False. Then, all() takes this list and short-circuits at the first element. Running some timing on these functions:

import timeit

timeit.timeit('f1()', setup='from __main__ import f1, l', number=10000)
# Out: 0.006381300001521595

timeit.timeit('f2()', setup='from __main__ import f2, l', number=10000)
# Out: 5.257489699986763

f1() is significantly faster than f2().

Alex P
  • 1,105
  • 6
  • 18