Proofs: Banker's queue
These are the human proofs of Banker's queue, at full length.
The banker's queue takes O(1) amortized time per operation, also under persistent use full
This proof shows: time.amortized.
We count one unit for each step of work. The unshared cost of an operation is the work it does outside the forcing of suspensions: it is for each of EMPTY, SNOC, HEAD, TAIL and CHECK, since line L9 only builds two suspensions. The shared cost is the work done when a suspension runs for the first time. Memoization means that each suspension runs at most once in the whole execution, whatever the number of versions that force it.
Costs of the suspensions. A rotation of a front of length and a rear of length makes the stream . Its cells are suspensions made one by one: the cell of index does own work (it forces cell of , which is a suspension of an older stream with its own debits, and builds the next suspension of the concatenation). The cell of index forces the end of , then runs REVERSE, which does steps, and returns its first cell. The cells after index were built by REVERSE and cost nothing more. We give each suspension debits equal to its own work, in units: the cell gets 1 debit and the cell gets debits (one for its own step and for REVERSE). All debits of a stream are attached to it when the rotation creates it, even to cells that are built later.
The accounting rule. Each operation may discharge some debits of the version it acts on. The record of discharged debits belongs to a version: the new version inherits the record of its argument and adds the debits that the operation discharges. The rule is that a suspension may be forced by an operation on a version only when, in the record of , all debits of that suspension are discharged. The amortized cost of an operation is its unshared cost plus the number of debits it discharges.
Lemma 1 (the rule bounds the total). If the rule holds in an execution, the total actual cost of the execution is at most the sum of the amortized costs of its operations.
Proof. The total actual cost is the sum of the unshared costs plus, for each suspension that runs, its own work . Take a suspension that runs, and the operation that first forces it, on a version . By the rule, all debits of are discharged in the record of . Each entry of that record was added by one operation in the history of , and that operation counted it in its amortized cost. Distinct suspensions have distinct debits, so these discharge events are distinct for distinct . So is at most the total number of discharges, and the total actual cost is at most the sum of the amortized costs. The same debit may be discharged in several histories; that only makes the bound larger, not wrong.
The debit invariant. For a version with front of length and rear length , let be the number of debits on cell of that are not yet discharged in its record, and . The invariant is
Each operation discharges the earliest debits first: to discharge debits, take them from the cell of least index with a debit left, then the next one, and so on. If is more than the debits left, all of them are discharged. After discharging debits in this way, the new prefix sums are .
Lemma 2. SNOC discharges 1 debit, or 2 if it makes a rotation, and TAIL discharges 2 debits. Then (D) holds for every version.
Proof. EMPTY has no cells, so (D) holds. Assume (D) for the argument .
Snoc without a rotation. The front is the same and grows by 1, so the bound falls by 1 and does not change. After the discharge of 1 debit, . If then . If then , and and because no rotation means .
Tail without a rotation. The new front is without cell 0, so the new cell is the old cell , and by (D) at . The new prefix sums before the discharge are , and . The new bound is , which is at least . After the discharge of 2 debits, if then is within the new bound; if then the new prefix sum is 0, and because no rotation means .
An operation with a rotation. A rotation happens when, after the update of the lengths, . For a snoc this means in ; for a tail it means in as well, since the tail lowers by 1. In both cases (D) for gives for every , so every cell of the front of is free of debits, and so is every cell of the shorter front after a tail. The new version has the new stream of length , where is the length of the old front after the operation, and . Its only debits are those of the rotation: for and for . After the discharge of 2 debits, for , and for all . For , . So (D) holds. (When the stream has one cell with 2 debits, and both are discharged.)
HEAD does not change the version, so (D) still holds for it.
Lemma 3. The accounting rule holds.
Proof. HEAD and TAIL force only the first cell of the front of their argument. By (D) at , , so that cell has no debits left. Forcing cell 0 of a rotated stream forces cell 0 of its inner front , which had no debits at the rotation (proof of Lemma 2), and the cell forces the end of and REVERSE, whose debits are on cell itself. A cell of index of a stream is forced only when it has become cell 0 of the front of some version, after the tails that removed the cells before it; then (D) for that version applies to it. No other operation forces a suspension.
Theorem. Any sequence of operations on a banker's queue, applied to any versions in any order, takes time in total. So each operation takes amortized time, also under persistent use.
Proof. Each operation has unshared cost and discharges at most 2 debits, so its amortized cost is . By Lemma 3 the accounting rule holds, and by Lemma 1 the total actual cost is at most the sum of the amortized costs, which is .
The banker's queue keeps its invariant and implements a persistent queue full
This proof shows: correctness.
We use the pseudocode of the page. A stream denotes the finite sequence of the items of its cells; forcing a suspension does not change what it denotes, only whether its value is stored. For a list , is its reverse, and is concatenation of sequences. For a queue let
A tuple is valid if , and .
Lemma 1 (CHECK). Let satisfy , and . Then CHECK returns a valid tuple with .
Proof. If , line L8 returns the same tuple, which is then valid, and the sequence is the same. Otherwise , and line L9 returns . The lazy concatenation denotes (by induction on : if is empty its value is the value of , and otherwise its first cell holds the first item of and its rest is the concatenation of the rest of with ). REVERSE denotes . So the new front denotes , its length is , the new rear is empty with length 0, and . The tuple is valid, and its sequence is .
Lemma 2 (operations). Let be valid with .
- EMPTY returns a valid tuple with sequence .
- SNOC returns a valid tuple with sequence .
- If , HEAD and TAIL report an error. If , HEAD returns , and TAIL returns a valid tuple with sequence .
Proof. (1) Line L1 returns , which is valid with the empty sequence.
(2) Line L3 calls CHECK on . Here and , so Lemma 1 applies, and the result is valid with sequence .
(3) Since is valid, , so if and only if . If , lines L4 and L6 report the error. If , then , so is non-empty and its first item is the first item of , which is ; line L5 returns it. In line L7 the rest of has and . The arguments of CHECK satisfy , so by Lemma 1 the result is valid with sequence .
Lemma 3 (no old version changes). No operation changes the sequence of a tuple that existed before it.
Proof. The operations create new tuples, new list cells (line L3) and new suspensions (line L9). They never assign to a field of an existing tuple, cell or suspension. The only change to existing objects is the memoization of a forced suspension, which stores the value that the suspension denotes and so does not change the sequence of any stream.
Theorem. Every version that a program builds from EMPTY by SNOC and TAIL, applied to any earlier versions in any order, is valid, and HEAD, TAIL and SNOC act on each version as the persistent queue type specifies.
Proof. By induction on the number of operations performed. EMPTY gives a valid version by Lemma 2(1). Each later operation takes as argument a version that was valid when it was made, and by Lemma 3 it still has the same fields and sequence, so it is still valid. Lemma 2(2) and 2(3) then give the result of the operation and the validity of the new version.