Left-right fold equivalence 2026-10-31
For finite lists, if and , then the right fold with step and seed equals the left fold with step and seed . The two steps need not be identical. Equality of the resulting values does not imply equal time complexity; list concatenation is an important example.
Linear flattening of a binary tree 2026-10-24
For a leaf-labelled binary tree, define
go (One x) acc = x:acc and go (Two l r) acc = go l (go r acc), then apply go t []. The invariant in the title follows by structural induction and associativity of list concatenation. It proves preservation of leaf order. Each leaf supplies one output constructor and each internal node requires constant bookkeeping, giving linear time in the number of nodes. By contrast, flattening both subtrees and concatenating their results can copy prefixes of sizes on a left-branching tree, taking quadratic time. The accumulator is the already-known suffix in a difference list representation. List-based set representation 2026-10-31
Represent a finite set by a list of its elements. Allowing repeated elements makes set union cheap by list concatenation; imposing uniqueness requires membership checks. A representation sorted by a total order and containing no duplicates permits set union and set intersection by a simultaneous scan in linear time. These are different representation invariants for the same abstract set.
List reversal 2026-10-31
Reverse the order of a finite list. Reversal is an involution and satisfies for list concatenation. In an array segment of length , exchanging symmetric offsets and for realizes reversal in constant auxiliary space.
Past exam of the computer science course of the University of Oxford 2015 Compilers 1 b Solution 2026-10-31
Take the lexical tokens
ID, ASSIGN, SEMI, BEGIN, END, CASE, OF, COLON, and BAR, corresponding to identifiers and the displayed punctuation or keywords. Let E be the already defined context-free grammar category for a program expression, with attribute E.tree; expressions do not consume statement delimiters except inside their own explicitly delimited constructs. ID.name is the recognized program identifier. The following context-free grammar uses S for a statement, C for a closed elementary statement, D for a nonempty sequence of closed statements, and A for a nonempty arm list. The braces in this display are semantic actions, not input lexical tokens.S -> C { S.tree = C.tree }
S -> C SEMI S1 { S.tree = Seq(C.tree, S1.tree) }
S -> CASE E OF A { S.tree = Case(E.tree, A.arms) }
C -> ID ASSIGN E { C.tree = Assign(ID.name, E.tree) }
C -> BEGIN S END { C.tree = S.tree }
D -> C { D.tree = C.tree }
D -> C SEMI D1 { D.tree = Seq(C.tree, D1.tree) }
A -> E COLON S { A.arms = [(E.tree, S.tree)] }
A -> E COLON D BAR A1 { A.arms = [(E.tree, D.tree)] ++ A1.arms }Here
++ is list concatenation, and distinct suffixes distinguish occurrences of the same nonterminal symbol. Every statement and arm body is nonempty; no empty statement or optional trailing semicolon is needed for the supplied language.The distinction between
D and S resolves both possible sources of grammar ambiguity. First, an unbracketed CASE can occur only at the right end of an S: there is no production S -> S SEMI S. Consequently all semicolon-separated statements after such a CASE enter its last arm. Enclosing that whole CASE in BEGIN ... END turns it into a C, which can be followed by another statement outside the CASE.Second, an arm followed by another arm at the same level must have a
D body. Such a body contains no unbracketed CASE. If an arm body does contain an unbracketed CASE, only the final-arm production A -> E COLON S can apply, and every following BAR before the enclosing END or end of input must be consumed by the inner CASE. Thus each bar belongs to the nearest preceding CASE whose block has not ended. A CASE inside BEGIN ... END is hidden from that rule outside its block.For uniqueness, at any statement level an initial
ID or BEGIN determines a C; an immediately following SEMI determines the recursive sequence production. An initial CASE determines the third S production. In an arm, a bar at the current block level either follows an entirely closed body, forcing the second A production, or follows an unbracketed nested CASE, forcing it into that inner arm list. With an unambiguous expression sublanguage these choices leave a unique parse tree. Closed bodies before an outer bar, and a possibly open body only in the final arm, enforce the required interpretation. Thus the context-free grammar implementsUse pattern matching to separate the empty list from a head and tail. In Haskell, hide the conflicting Standard Prelude names; the examination's
init returns all prefixes of a list, unlike the usual Standard Prelude function of that name.import Prelude hiding (map, length, init, tails, foldr, compare)
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
length :: Num n => [a] -> n
length [] = 0
length (_:xs) = 1 + length xs
copy :: (Eq n, Num n) => n -> a -> [a]
copy 0 xs = []
copy n xs = xs : copy (n-1) xs
combine :: [[a]] -> [[a]] -> [[a]]
combine [] _ = []
combine _ [] = []
combine (xs:xss) (ys:yss) = (xs ++ ys) : combine xss yss
init :: [a] -> [[a]]
init [] = [[]]
init (x:xs) = [] : map (x:) (init xs)
tails :: [a] -> [[a]]
tails [] = [[]]
tails (x:xs) = (x:xs) : tails xsThe map function preserves the input list shape while transforming each entry.
length counts the cons cells. copy repeats the whole value xs, so for the specified use its second argument is a list and the result is a list of lists. Its inferred type is more general: the repeated value can have any data type. If one restricts the interface to lists, the corresponding type is (Eq n, Num n) => n -> [a] -> [[a]]. The numeric argument has the precondition that it is a nonnegative integer; the most general inferred numeric constraints do not guarantee termination on arbitrary values of their instances. An explicitly integral interface may instead use Integral n => n -> [a] -> [[a]].combine performs list concatenation head by head and stops as soon as either outer list ends. For init, every nonempty prefix of a list starts with x and continues with a prefix of a list from xs; prepending [] supplies the shortest prefix of a list. For tails, the longest suffix of a list is the whole input, followed by all suffixes of a list of its tail. For example, init [1,2] = [[],[1],[1,2]] and tails [1,2] = [[1,2],[2],[]]. Thus both include the empty list, andUse structural induction on the first finite outer list
xss, with the assertion quantified over every finite yss. For xss = [], both sides are []. For the nonempty case of structural induction, write xss = xs:xss'. If yss = [], both sides again truncate to []. Otherwise write yss = ys:yss'. Expanding the definitions givesThe second equality uses the defining equation for list concatenation and the induction hypothesis for the smaller outer list xss'. The argument covers unequal outer lengths because the empty second list was handled explicitly. HenceEach entry contributes a choice between omission and inclusion. Use list concatenation as , so that Cartesian lifting of a binary operation to lists joins every choice from an earlier position to every choice from later positions:
crossAppend :: [[a]] -> [[a]] -> [[a]]
crossAppend xss yss = [xs ++ ys | xs <- xss, ys <- yss]
f :: a -> [[a]]
f x = [[],[x]]
subs :: [a] -> [[a]]
subs xs = foldr crossAppend [[]] (map f xs)The seed is
[[]], because the empty list has one subsequence, namely [], and concatenating the empty list changes nothing. If rest lists the subsequences of the tail, crossAppend [[],[x]] rest lists all omissions of x first and all inclusions second. For [a,b,c], the last position changes fastest, producing the specified order [], [c], [b], [b,c], [a], [a,c], [a,b], [a,b,c]. Repeated input values can give equal subsequences at distinct choices of positions; those repetitions are retained. ThusThe map function transforms each head and recursively processes the tail. A right fold combines a head with the result for its tail, whereas a left fold passes an updated accumulator into the recursive call. In Haskell:
import Prelude hiding (map, foldr, foldl, concat)
map f [] = []
map f (x:xs) = f x : map f xs
foldr op e [] = e
foldr op e (x:xs) = op x (foldr op e xs)
foldl op e [] = e
foldl op e (x:xs) = foldl op (op e x) xs
concat = foldr (++) []The last right fold uses list concatenation to replace each outer cons by appending its inner list. Thus
concat [xs,ys,zs] = xs ++ (ys ++ (zs ++ [])). Its right fold association matters: appending a list traverses its left spine, so these equations traverse each inner spine once on finite inputs. The concise answer isLet mean
concat (map f zss) = f (concat zss). We use structural induction on lazy lists, including the bottom value and allowing arbitrary, possibly partial, inner lists.For
[], the left side is [] and the right side is f [] = []. For the bottom value , both map f and concat are strict functions in the outer spine, and the supplied strictness of makes the right side too.For the cons case, assume and write the new head as
xs. The defining equations and the induction hypothesis giveThe third equality is exactly the supplied list concatenation preservation law. No inspection of xs was required, so the argument includes partial inner lists and undefined elements.This proves the identity for every finite outer spine ending in either
[] or . In the usual pure Haskell semantics, these functions are Scott continuous maps. An infinite outer list is obtained as a limit by finite approximation of a lazy list; both composite functions preserve that limit. Equality on all its finite approximations therefore gives equality on the infinite outer list. Equivalently, is an admissible predicate on lazy lists. ConsequentlyThe continuity assumption is the usual semantic assumption for definable functions here; an extension from finite to infinite inputs cannot be justified by ordinary finite induction alone.Let be the two input lengths and suppose numeric comparisons cost constant time. The list-based set representation determines which representation invariant each answer must preserve.
For unsorted lists allowing repetition, retain every left-hand occurrence whose value appears on the right for set intersection, and use list concatenation for set union:
interR xs ys = [x | x <- xs, x `elem` ys]
unionR xs ys = xs ++ ysThe resulting lists represent exactly the desired sets; repeated entries are permitted. Membership by sequential search makes
interR take time complexity in the worst case. The list concatenation in unionR costs to build its copied left spine and shares ys; fully enumerating the resulting list costs .For unsorted, duplicate-free inputs, the same intersection keeps uniqueness, while the union retains
xs and appends only right-hand elements absent from xs:interU xs ys = [x | x <- xs, x `elem` ys]
unionU xs ys = xs ++ [y | y <- ys, not (y `elem` xs)]Neither filtered list has internal repetitions, and the filter in
unionU also excludes overlap with xs. Both operations have worst-case time complexity, customarily written when both lengths are positive. Using the original xs for each membership test avoids searching an ever-growing output.interS [] ys = []
interS xs [] = []
interS (x:xs) (y:ys)
| x < y = interS xs (y:ys)
| x > y = interS (x:xs) ys
| otherwise = x : interS xs ys
unionS [] ys = ys
unionS xs [] = xs
unionS (x:xs) (y:ys)
| x < y = x : unionS xs (y:ys)
| x > y = y : unionS (x:xs) ys
| otherwise = x : unionS xs ysWhen , sortedness proves that cannot occur anywhere in the right list, so it can be discarded from the set intersection or immediately emitted to the set union. The symmetric argument handles ; equal heads are consumed together. Each step consumes at least one input head, so both have worst-case time complexity. These bounds assume the sorted representation invariant is already established; sorting arbitrary inputs has its own cost.
| Representation | Set intersection | Set union, including output traversal |
|---|---|---|
| Unsorted, repetitions allowed | ||
| Unsorted, unique | ||
| Sorted, unique |
List concatenation exhausts one inner list before reaching the next. For example, with rows
[0,2..] and [1], concat outputs the infinite even row and never reaches 1. First-occurrence duplicate removal cannot recover a value that its input stream never supplies. HenceThe obstruction is separate from the undefined final tail in part (c): here a required distinct value is never emitted at all. A fair enumeration must give every row and every element within it a finite opportunity to contribute.The map function changes each element of a list while preserving its order and length. List concatenation removes one level of nesting. Currying changes a function on a pair into a function taking its two arguments separately; uncurrying reverses this conversion:
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
concat :: [[a]] -> [a]
concat [] = []
concat (xs:xss) = xs ++ concat xss
curry :: ((a, b) -> c) -> a -> b -> c
curry f x y = f (x, y)
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry f (x, y) = f x yThese polymorphic type signatures impose no numerical, equality or ordering constraints: the definitions only inspect list or pair structure and apply a supplied function. In particular,The recursive equations describe the two possible list constructors, so they also explain how the empty case terminates each finite computation.
Set both and equal to list concatenation, with . Associativity gives
xs ++ (ys ++ zs) = (xs ++ ys) ++ zs, which is equation (1). The two identity laws give xs ++ [] = [] ++ xs, which is equation (2). Part (b) therefore yieldsconcat :: [[a]] -> [a]
concat = foldl (++) []Write , with . At the th step of the left fold, the left operand of list concatenation is the accumulated prefix of length . Fully forcing these concatenations repeatedly copies earlier elements. Including the traversal of the outer list and the whole output spine givesFor singleton inner lists, this becomes , whereas the right fold takes . More generally, for equal positive length , the left fold takes .
An bound is tight in the worst case over length distributions, but is not a tight formula for every distribution: if all elements occur only in the final inner list, all earlier accumulators are empty and the cost is . Lazy evaluation changes when the work happens; it does not remove repeated prefix traversal when the whole result is demanded.
Let there be inner lists, of lengths , and let . Evaluate the whole output list spine, taking each input element as an already available value. The defining equation
(x:xs) ++ ys = x : (xs ++ ys) shows that list concatenation traverses its left operand, sharing its right operand.The right fold puts each original inner list on the left of exactly one concatenation. Thus its total element traversal is proportional to , and visiting the outer list costs :The term matters when many inner lists are empty. With rows each of length , the bound is . In particular, with uniformly bounded positive row length it is linear in .
Use the empty list for an empty subtree, and join the left result, the current label and the right result:
flatten :: STree a -> [a]
flatten = foldSTree (\left x right -> left ++ (x : right)) []Thus . This is an inorder traversal. By structural induction, each subtree produces its labels in order; the binary search tree invariant places every left label below and every right label above , proving that the whole result is increasing. The definition works for any binary-tree shape, although its result is sorted only when the ordering invariant holds. Repeated list concatenation can give quadratic cost on a left chain; the question asks for a tree fold definition, rather than an optimal traversal implementation.
Use an abstract finite sequence of integers, initially empty. In the following trait, comments are the mathematical specification; means the abstract state on entry, and The postcondition of
result means the returned value.trait ReversableBuffer {
// Abstract state s: finite sequence of Int, initially [].
// Pre: true. Post: s = s0 ++ [x].
def append(x: Int): Unit
// Pre: true. Post: s = [x] ++ s0.
def prepend(x: Int): Unit
// Pre: 0 <= i < length(s0).
// Post: result = s0 !! i,
// s = take i s0 ++ drop (i+1) s0.
def get(i: Int): Int
// Pre: true. Post: s = reverse(s0).
def rev: Unit
}get includes removal: it is not merely indexed reading. In particular,Repeated equal values elsewhere in the sequence remain present. List concatenation, indexing and list reversal in the specification describe abstract behavior without committing to a physical representation.The child results supplied by the tree fold are already their depth-first traversals. Prepend the current label to their list concatenation:
dftF :: RTree a -> [a]
dftF = foldRTree visit
where
visit :: a -> [[a]] -> [a]
visit x childLists = x : concat childListsThus . Substituting the equation for the tree fold gives exactly the recursive preorder traversal equation from part (i), proving equality by structural induction on a finite rose tree.