1

The invariant used for this example comes from https://www.win.tue.nl/~kbuchin/teaching/JBP030/notebooks/loop-invariants.html

I'm confused though. The code in the example uses a for loop.

I translated it to a while loop, because I understand this better, and I added assertions to test the invariant.

The while example makes total sense to me, and I can see how the invariant holds at each of the assertion points.

However, in the for example, the assertion assert total == sum(A[0:i]) and i >= len(A) fails.

I can see why it might for the i value, as i stops incrementing at 4. But I'm not sure why the final assertion for the sum needs to be assert total == sum(A[0:i + 1]).

It seems like quite a subtle thing going on here, around "off by one errors." I'm also a bit uncomfortable about the "hard-coding" of the invariant in assert total == sum(A[0:0])

Can anyone please provide an exact conversion of the while version of my code to a for version, with all the neccessary assersions for the invariant, and an explanation of how/why they are different?

Any help much appreciated.

def my_sum_while(A):
    """
    Loop Invariant: At the start of iteration i of the loop, the variable
    `total` should contain the sum of the numbers from the subarray A[0:i].
    """
    i = 0
    total = 0
    assert total == sum(A[0:i])
    while i < len(A):
        assert total == sum(A[0:i])
        total += A[i]
        i += 1
    assert total == sum(A[0:i]) and i >= len(A)
    return total
    
xs = [1, 2, 3, 4, 5]
print(my_sum_while(xs))

def my_sum_for(A):
    """
    Loop Invariant: At the start of iteration i of the loop, the variable
    `total` should contain the sum of the numbers from the subarray A[0:i].
    """
    total = 0
    assert total == sum(A[0:0])
    for i in range(len(A)):
        assert total == sum(A[0:i])
        total += A[i]
    assert total == sum(A[0:i]) and i >= len(A)
    return total
    
xs = [1, 2, 3, 4, 5]
print(my_sum_for(xs))
Robin Andrews
  • 3,514
  • 11
  • 43
  • 111
  • I get an `AssertionError` traceback on the for loop version. Line 29 `assert total == sum(A[0:i]) and i >= len(A)` I guess because `i` only lives inside the for loop and the assertion is outside it. – Cow Aug 18 '22 at 12:23
  • In Python, `a[x: y]` means the slice of `a` starting at the `x`th element, up to *but not including* the `y`th element. So if `len(A)` is 5, then `i` reaches a final value of 4 (as you observed) and the sum is `A[0] + A[1] + A[2] + A[3] + A[4]`. That's not the same as `sum(A[0:4])` which excludes `A[4]`. – slothrop Aug 18 '22 at 12:23
  • @user56700 That is my point - I want to correct the assertions and understand how and why this needs doing. – Robin Andrews Aug 18 '22 at 12:27
  • @slothrop Yes, the while loop version accounts for this, but the for loop version needs fixing and I'm trying to understand why the invariant seems to change in the for loop version. – Robin Andrews Aug 18 '22 at 12:28
  • @RobinAndrews the `while` loop increments `i` on its last line, so after adding the final element `A[4]` to the total, it increments `i` to 5. The `for` loop doesn't do that: it iterates over exactly the values in `range(5)`, i.e. 0,1,2,3,4. That's the key difference. – slothrop Aug 18 '22 at 12:30
  • btw, for both loops, the preassertion could be written as `assert total == sum([])`. That seems clearer if anything: "before we have added any elements at all,`total` must equal the sum of an empty collection". – slothrop Aug 18 '22 at 12:49
  • @slothrop I can see what you mean about `[]`, but my understanding is that the invariant should be the same before, during and after the loop. I'm going to make a suggestion on the answer given below to clarify this. – Robin Andrews Aug 18 '22 at 13:42

1 Answers1

5

While Loop Variant

In your while loop i+=1 adds 1 to i and then checks the condition for while loop to continue or break. In given example condition is while i < 4. So at last when i becomes 5 while loop breaks and you get i=5.
So when you use it in slice it becomes [0:5] which will return element from index 0-4.

For Loop Variant

Compare this to for loop variant for loop will only iterate i from 0 to 4 and it will stop. Hence, value of i is 4 once for loop breaks.
So when you use it in slice it becomes [0:4] which will return element from index 0-3. Which in any case is not equal to total.

Final Analysis

  • while breaks when i>4 and hence i becomes 5 and it breaks.
  • for loop will iterate only from 0 to 4 and will break at 4.
  • Hence value of i is different at the end of while and for loop.

Changes to get same behaviour for both the loop

  • You need to add i+1 outside for loop variant to get same as while.
  • And vice versa if you want while loop to act like for loop check for condition with i==len(A) which will break the loop at i=4 itself.
GodWin1100
  • 1,380
  • 1
  • 6
  • 14
  • I'm thinking to keep the invariant truly invariant in the for loop, we could initialise I to 0 before the first assertion and keep it in the slice instead of 0, and then increment it by one before the final assertion. That seems clean to me. What do you think? – Robin Andrews Aug 18 '22 at 13:46
  • There's not any issue in slice or initializing `i`, just increment `i` by one before the final assertion for `for` loop to get same logic as `while` loop. It does seems clean to me also. – GodWin1100 Aug 18 '22 at 16:27