Bulkilol
Bulkilol

Reputation: 151

Ambiguous list comprehension ranges in python

I want to generate all tuples (m, n) such that m+2n <= 2nmax - 1 in python 3.9. It is easily done via

nmax = 4
a = [(m, n) for n in range(0, nmax) for m in range(0, 2*(nmax-n))]
>>> [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (0, 2), (1, 2), (2, 2), (3, 2), (0, 3), (1, 3)]

However I noticed that if I instead do

nmax = 4
b = [(m, n) for n in range(0, nmax+1) for m in range(0, 2*(nmax-n))]
>>> [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (0, 2), (1, 2), (2, 2), (3, 2), (0, 3), (1, 3)]

(n goes to nmax+1 instead of nmax) then

a == b
>>> True

I first thought it was an edge case for the nmax I was picking, but I get the same behaviour for all nmax<100. I do not quite understand why there is an ambiguity here. Is it because of the range of m depends on n? If so, what would be the "correct" bounds?

Upvotes: 1

Views: 179

Answers (2)

pho
pho

Reputation: 25489

In the last iteration of your second case, n is equal to nmax. This makes 2 * (nmax - n) zero. range(0, 0) is an empty iterator, so there is no inner loop for the last outer loop of n, so nothing gets added to the list comprehension, and it's as if the last loop never happened. So you get a == b is True.

You want m <= 2(nmax - n) - 1, i.e. m < 2(nmax - n), which is what you already get from your first loop.

When n == nmax, this condition gives m <= -1. Using m in range(0, ...) automatically means m >= 0. It's fairly obvious that both these conditions can never be true, so no pair of (m, n) will satisfy m, n >= 0; m + 2n <= 2 nmax - 1 when n == nmax

Upvotes: 2

azro
azro

Reputation: 54148

Is it because of the range of m depends on n

Yes.


  1. Outer range(0, nmax) means last value is nmax-1 that means inner loop final bounds is

    2 * (nmax - n)
    2 * (nmax - nmax + 1)
    2 * 1 
    2
    
  2. Outer range(0, nmax + 1) means last value is nmax that means inner loop final bounds is

    2 * (nmax - nmax)
    2 * (0)  
    0
    

So the round you add for n creates an empty range for m


Detail by the values

for n in range(0, nmax + 1):
    values = list(range(0, 2 * (nmax - n)))
    print(f"{n=}   {2*(nmax-n)=}   {values=}")


n=0   2*(nmax-n)=8   values=[0, 1, 2, 3, 4, 5, 6, 7]
n=1   2*(nmax-n)=6   values=[0, 1, 2, 3, 4, 5]
n=2   2*(nmax-n)=4   values=[0, 1, 2, 3]
n=3   2*(nmax-n)=2   values=[0, 1]
n=4   2*(nmax-n)=0   values=[]                    # generated by the +1 in the 'n' range

Upvotes: 2

Related Questions