Given an array A.(0 ≤ i < N) of nonnegative integers, that is:
(0) 〈∀ i : 0 ≤ i < N : A.i ≥ 0〉
and a positive integer s > 0, we want to find a segment of A that sums to s, if it exists. Define:
S.i.j = 〈∑ k : 0 ≤ i ≤ k < j ≤ N : A.k〉
The gematric search for s in A is to find, if possible, a pair (p, q) such that S.p.q = s (it is hoped that conoisseurs would readily make the connection). Note a number of things:
- S.i.j is a total function on i, j
- By definition, the sum on an empty range is zero:
(1) S.i.i = 0
for all integer i.
- For arguments in range of A, S.i.j is monotone on its second argument and antitone on its first. That is, given 0 ≤ i′ ≤ i < j ≤ j′ ≤ N:
(2) S.i.j ≤ S.i.j′ (3) S.i.j ≤ S.i′.j
What should the result be if no such segment is present in A? One possibility would be setting a flag found, such that the program ensures:
¬found ∨ S.p.q = s
An alternative would be to guarantee some condition on (p, q) equivalent by design to found. Given that s ≠ 0, one such condition would be satisfied by S.p.q = 0 as a suitable sentinel. By (1), this is implied by p = q, and we choose the weaker postcondition:
(4) p = q ∨ S.p.q = s
Now (4) is trivially satisfied by:
{ A.(0 ≤ i < N) ∧ s > 0 } gemsearch ≡ p := 0; q := 0; sum := 0; { reduce |sum - s| under invariance of (4) } { p = q ∨ S.p.q = s }
where sum = S.p.q at each step. How should we meet the refinement obligation in the program? We clearly have three cases:
- sum = s
We have found our segment, and we can stop looking.
- sum < s
By (2) we can extend the segment on the right, provided we can, that is, if q ≠ N.
- sum > s
By (3) we can shrink the segment on the left, provided we can, that is, if p ≠ q. But since s > 0, this is implied by p ≠ N, a weaker condition symmetric to the previous one and thus preferable. It is also suitable for the repetition guard, since its negation implies the postcondition.
We have:
{ A.(0 ≤ i < N) ∧ s > 0 } gemsearch ≡ p := 0; q := 0; sum := 0; do p ≠ N ∧ sum ≠ s → if sum < s ∧ q ≠ N → sum := sum + A.q; q := q + 1 [] sum > s → sum := sum - A.p; p := p + 1 [] … → ? fi od { p = q ∨ S.p.q = s }
The conditional is not exhaustive. Let's calculate what the obligations are for third case:
¬((sum < s ∧ q ≠ N) ∨ sum > s) ≡ { DeMorgan, twice } (sum ≥ s ∨ q = N) ∧ sum ≤ s ≡ { Distribute } (sum ≥ s ∧ sum ≤ s) ∨ (q = N ∧ sum ≤ s) ≡ { Tertium non datur } sum = s ∨ (q = N ∧ sum ≤ s) ⇐ { Loop condition } q = N ∧ sum < s
This condition expresses a deficient suffix of A which is, by (3), as large as it can be, hence we can quit the search. Left-factoring the common condition on s, we arrive at our final program:
{ A.(0 ≤ i < N) ∧ s > 0 } gemsearch ≡ p := 0; q := 0; sum := 0; do p ≠ N ∧ sum ≠ s → if sum < s → if q ≠ N → sum := sum + A.q; q := q + 1 [] q = N → p := N fi [] sum > s → sum := sum - A.p; p := p + 1 fi od { p = q ∨ S.p.q = s }
The algorithm is of complexity obviously linear on N, since no element of A is accessed more than twice.