University of New South Wales
COMP 6721
(In-)Formal Methods: The Lost Art
2021 Term 2
Assignment 1 —
Respecting and exploiting
assertions and invariants
Due 18:00 (6pm) on Sunday 27 June 2021
1 Typsetting a paragraph in LATEX style
LATEX typesets paragraphs so that they fill each line exactly, at both the left-
and right-hand sides (as for this paragraph). It does that by putting just enough
blank space between words to get the desired width overall. That’s the program
we will be writing in this assignment, more or less — but our program will only
approximate LATEX’s approach, because we will be using a fixed-width font.
As you will see, however — the assignment is not about how to typeset.
It’s about how to write programs using assertions and invariants, both to help
find the program and to increase the likelihood that it is correct. Typesetting
just happens to be an intricate-enough problem to make it interesting.
Figure 1(a) shows how some text in a fixed-width font can be made into a
paragraph exactly 37 columns wide (except for the last line). 1 The “greedy”
typesetting (at left) fills each line as much as possible before moving on to the
next. Then enough extra space is added to each line except the last, between
the words, so that every line finishes exactly at the right-hand margin.
In general, greedy algorithms take decisions based on the “current” situation,
and do not look ahead to see whether a different decision now might lead to a
better outcome later on. We can do better than greedy (as Knuth showed).
In Fig. 1(b) the same text is typeset (at right), but now using a “clever”
algorithm –based on dynamic programming– that optimises (in this case, min-
imises) the average amount of extra space that must be added between words
in each line.
1 The text is the abstract of
Donald E. Knuth and Michael F. Plass. Breaking Paragraphs into Lines.
Software Practice and Experience 11(1119–84), 1981.
which describes the way that TEX and (thus) LATEX make paragraphs.
1
How LATEX sets paragraphs
abstract from Donald E. Knuth and Michael F. Plass.
Breaking Paragraphs into Lines.
This paper discusses a new approach
to the problem of dividing the text
of a paragraph into lines of
approximately equal length. Instead
of simply making decisions one line
at a time, the method considers the
paragraph as a whole, so that the
final appearance of a given line
might be influenced by the text on
succeeding lines. A system based on
three simple primitive concepts
called boxes, glue, and penalties
provides the ability to deal
satisfactorily with a wide variety of
typesetting problems in a unified
framework, using a single algorithm
that determines optimum breakpoints.
The algorithm avoids backtracking by
a judicious use of the techniques of
dynamic programming. Extensive
computational experience confirms
that the approach is both efficient
14/3 → and effective in producing
high-quality output. The paper
concludes with a brief history of
line-breaking methods, and an
appendix presents a simplified
algorithm that requires comparatively
few resources.
1234567890123456789012345678901234567
(a) Greedy strategy: cost 14/3 = 4.67
This paper discusses a new approach
to the problem of dividing the
text of a paragraph into lines of ← text delayed
approximately equal length. Instead
of simply making decisions one
line at a time, the method considers
the paragraph as a whole, so ← 14/5
that the final appearance of a
given line might be influenced
by the text on succeeding lines.
A system based on three simple
primitive concepts called boxes,
glue, and penalties provides the
ability to deal satisfactorily with a
wide variety of typesetting problems
in a unified framework, using a
single algorithm that determines
optimum breakpoints. The algorithm
avoids backtracking by a judicious
use of the techniques of dynamic
programming. Extensive computational
experience confirms that the approach
is both efficient and effective
in producing high-quality output.
The paper concludes with a brief
history of line-breaking methods, and
an appendix presents a simplified
algorithm that requires comparatively
few resources.
1234567890123456789012345678901234567
(b) Clever strategy: cost 14/5 = 2.80
The paragraph above is set in a column of 37 spaces over 29 lines in both cases:
but a greedy strategy is used on the left and a clever one on the right.
The greedy paragraph took text into the second line “because it could”, but
paid for it later with ugly extra white space in the last ten lines.
The cost of a typesetting is the maximum over all lines (except the last) of the
average white space between adjacent words on each line separately.
Figure 1: Greedy vs. clever typesetting strategy
In this assignment you will program a clever paragraph-maker in Python3.
Be sure however to read Sec. 8 below carefully — it describes how
the assignment will be marked (including penalties, if applicable, for
late submission).
2 Assertions and invariants
Although this assignment asks you to write the clever, dynamic-programming
algorithm, and test it, it is not about that algorithm itself, nor is it
about dynamic programming. (You will learn more about those things in
other courses.)
This assignment is about how to use assertions, invariants and static reason-
ing 2 to write complicated programs and, by doing so, increase the chance that
they will be correct. The dynamic programming we do here is just one example
of where it really helps. Thus in this assignment there won’t be a “test suite”
to check that your program is working correctly, and it won’t be run through
an “auto marker”. Instead, it’s your actual source code that will be marked, as
for an an essay, and your mark will depend on how well you have written that
code. (It is the normal approach for this course.)
Here therefore are some key points in the way the assignment is presented:
(a) Your code will be written in Python3. 3 (Not C, not Java, not anything
else.) That’s partly to make it feasible to mark, but also because of (b).
(b) You are given (below) an overall program structure to start with (written in
Python3), and it will have –as comments– some assertions written already.
(c) Your actual programming job will be to “Fill in the code between the as-
sertions.” That’s where you will gain your marks.
(d) Because of (b,c), your program will use the variable names chosen for you
already. If you need extra variables, of course you can name those yourself.
(There should be very few.) The principal ones however must remain as
given: do not change the variable names.
(e) The code templates that contain questions (Figs. 3,4,5) you should type in
yourself — really. They won’t be in a downloadable file. (You don’t have
to type in the comments; but consider including some of them anyway.)
That’s not only to ease the version-control problem between any extra .py-
files and this .tex-file, but as well for the more important reason that,
because typing takes time, you will be forced to think about what you
are doing. This whole assignment is about how to think effectively as you
program — and typing in the code (rather than c’ing/v’ing from somewhere
else ,) is one of the tricks of the trade.
2“Static reasoning” is –among other things– writing comments/assertions about “What’s
true here.” rather than “What is done here.”
3Python2 is no longer supported. Do not use anything other than Python3.
3
3 The general strategy of this algorithm
The program will be given a non-negative integer line-width M as a parameter
and an input-file of whitespace-separated words, over as many lines as you like.
Those words will be collected in a sequence wds of words (character strings).
The output of the program will be a paragraph, built from the words in
wds, in which all lines (except possibly the last) are exactly M characters wide,
achieved by adding blanks between words where necessary. The program prints
out that paragraph.
And the printed paragraph will be of optimal (minimum) cost, where
• the cost of a single line is the average number of blanks that are needed
between each adjacent pair of words, 4 and
• the overall cost is the largest cost of any single line (except the last).
For example, if the words on a line have lengths 4,5,9,1,3,8 (as in the first line
of Fig. 1) and the line-width M is 37, then 37 − (4+5+9+1+3+8) = 7 blanks
must be distributed over the 5 inter-word spaces. Since we can use only whole
blanks, we might mix 1- and 2-blank spaces in the pattern 1,2,1,2,1. Or perhaps
2,2,1,1,1; or maybe 1,1,1,2,2. But gaps-lengths must not differ by more
than one within a single line. Thus the pattern 1,1,1,1,3 is not allowed.
Your program will be in three principal parts:
Part (1) Starting from the end of the list of words, calculate the minimum
possible cost of making a paragraph for successively longer suffixes of
the word list. (That’s the dynamic-programming part.)
Part (2) Use the the results of (1), but now starting from the beginning of the list
of words, to split the word-list into lines such that each line fits within
the width and the cost of that line is no worse than the minimum
achievable for the whole paragraph (as calculated by Part(1)).
Part (3) Use the results of (2) to print out the actual paragraph, complete with
the extra blanks necessary so that each line (except the last) is exactly
M characters wide.
And there is a Part (0) part that is just concerned with reading the input.
4 Part (0) — Read the input.
The Python3 code of Fig. 2 reads the list of words from standard input, and the
line width from the command line. Make a .py file from it, and test it. (You
may copy-and-paste this part from the pdf; but you might need to tidy it up.)
4If there is only one word in a line, i.e. no “adjacent”, then the cost is the number of blanks
needed to fill the line.
4
### Paragraph.py --- Synopsis
#
# % python3 Paragraph.py WIDTH < WORDS
#
# The column WIDTH (non-negative integer) is given as the only argument;
# WORDS come from standard input, over as many lines as it takes, separated
# by white space. EOF from the terminal is ^D (as usual).
#
# The program prints WORDS in lines of exactly WIDTH characters, except
# possibly the last line, which might be shorter. It minimises the largest
# average white space between words in order to achieve the right-margin
# justification --- i.e. that the right margin is a straight vertical line.
#
# Although the average white space needed in a line might not be a
# whole number of blanks, the program simulates that in a fixed-width
# font by using a mixture of "just more than average" and "just less
# than average" inter-word gaps.
### Part (0) --- Collect input.
from sys import argv,stdin
# Get the line-width from the command line.
try:
assert len(argv)==2, "Expecting exactly one argument."
M= int(argv[1])
assert M >= 0, "Line width must be non-negative integer."
except: assert False, "Usage: python3 %s WIDTH"%argv[0]
# Get the words from standard input into wds, and lengths into wls.
wds= []
for line in stdin.readlines(): wds= wds+line.split()
wls= list(map(len,wds))
N= len(wds)
# Check that the input satisfies the program’s precondition.
for w in wls: assert w<=M, "1053: No word may be longer than WIDTH."
print("wds is",wds)
print("wls is",wls)
print("M is",M)
print("There are",N,"words in total.")
### Do not hand this file in on its own; DO test it. ###
You may copy-and-paste the code here directly from this pdf — but you will have to
fix some white-space copying errors afterwards. Only a few.
Figure 2: Part (0) — Read the program’s input data
Test your code by printing out what (you think) it has read in and has
taken from the command line: the words wds, and the line-width M. Print also
the sequence of word-lengths wls and the number of words altogether.
Do not hand in this part on its own. You will however have to hand it in as
part of the larger program, as explained in later parts of the assignment.
5 Part (1) — Calculate the minimum costs.
The dynamic-programming aspect of this algorithm is that, in order to minimise
(optimise) the cost of the whole paragraph, we actually find the optimum costs
for all its suffixes along the way. Thus we introduce a sequence of real numbers
cs[0:N] (for costs), and the postcondition of this Part (1) will be that
For all n in [:N] we have # [:N] is Python for 0,1,...,.N-1 .
cs[n] == "the optimal achievable cost of typsettting
suffix wds[n:] in line-width M."
# wds[n:] means "from wds[n] to the end of wds".
That means in particular that when the postcondition is established, the element
cs[0] will be the optimal achievable cost for the whole paragraph. (But we do
not yet know how to achieve that optimal cost.)
A code skeleton for this is shown in Fig. 3. The blue text is the assertion-
comments (including invariants) that you must respect when you write your
code. Typing those assertions in (as comments) as well will help you to make
sure that you have respected them. The red text identifies the places where you
must supply code.
Thus for Part (1), you should fill in the missing portions AAA · · · HHH of the
skeleton given in Fig. 3; the number after the three letters is the marks available
for that answer. Type the whole thing in; and test it together with the code
you used for Part (0) by including the print cs at the end to check those
values against your test inputs.
/ 1Put your code for Parts (0,1) into a single (text) file assn1P1.py. Include
your name and student number as a comment, inside the file, at its beginning.
(That means at the top: please don’t make me search for it ,.) File assn1P1.py
is the first component of what you will submit for marking — see Sec. 8.3 for
how to submit it. It will be marked based on how well your added red code
in Part (1) –that replaced AAA · · · HHH– respects the blue assertion-comments in
Fig. 3. If it does, then it should produce the correct output as shown by the
print cs — but do not hand your tests in.
Do make sure your program “so far” compiles and runs, however, since I
might test it myself if I am unsure whether the assertion-comments are being
respected. And –obviously– do not change the assertion-comments or change
any variable names; and do not remove any of the assert statements.
6
### COMP6721 2021T2 Assignment 1.
### YOUR NAME
### YOUR ZID
### Part (1) --- Calculate minimum cost cs[n] for each suffix wds[n:].
### But wds is not needed in this part; use wls.
def Fits(l,h): # Whether wds[l:h] would fit within line-width M.
assert 0<=l=0, "1723: Extra blanks must not be negative."
assert ls!=[], "1233: MakeLine(ls,b) requires ls!=[]."
# If there’s only one word, just put all blanks at the end.
if len(ls)==1: return ls[0]+(" "*(M-len(ls[0])))
# Otherwise, the usual case...
# b is the number of blanks to distribute,
# g is the number of inter-word gaps into which they must fit,
# and each gap must receive between floor(b/g) and ceil(b/g) blanks.
g= len(ls)-1
b1,g1= b,g
l= ls[0]
# INV5(b1,g1): NNN 3
for w in ls[1:]:
r= OOO 2 # Number of blanks to put in this gap.
assert floor(b/g)<=r<=ceil(b/g), "Space must not vary too much."
l= l+PPP 2
b1,g1= QQQ 2
# b1==0 here, because RRR 2
return l
### Do not hand this in on its own. Do test it. ###
Do not copy-and-paste this: type it in. Combine it with the earlier parts: do
not hand it in on its own.
Figure 5: Part (3)a — Stretch the lines out to M
### COMP6721 2021T2 Assignment 1.
### YOUR NAME
### YOUR ZID
### Part (3b) --- # Construct the lines of the paragraph,
# one-by-one, and print them out.
maxBlanks,lineCount= 0,0 # For reporting only.
for ws in wlss:
ls,wds= wds[:len(ws)],wds[len(ws):] # Get the actual words.
gapsHere= len(ws)-1
if wds==[]: line= MakeLine(ls,gapsHere); print(line) # Last line.
else:
blanksNeededHere= M-sum(ws)
line= MakeLine(ls,blanksNeededHere)
maxBlanks= max(maxBlanks, \
blanksNeededHere/gapsHere \
if len(ws)>1 else blanksNeededHere)
print("%s %d/%d = %.2f." % ( \
line, blanksNeededHere, \
gapsHere,blanksNeededHere/gapsHere \
if len(ws)>1 else blanksNeededHere)
)
lineCount= lineCount+1
print(" "*M+"^")
print(("==> 567890"+("1234567890"*floor(M/10)))[:M]+"|")
print("==> The maximum (fractional) white space for column width" + \
" %3d was %.2f over %d lines." % \
(M, maxBlanks,lineCount))
### Hand this in, together with the code from Figs. 2,3,4,5
### as a single assn1P3.py file.
### It is your complete program.
You may copy-and-paste this.
Figure 6: Part (3)b — Make the lines and print them
8 How this assignment will be marked.
Do not spend any time in Secs. 9, 10 unless you have completed the main as-
signment. You can get full marks for correct answers to AAA · · · RRR .
8.1 Testing — must be done; but who does it?
With luck, it will be you and not me: you are expected to test your program –of
course– but do not submit your test results. Still, if you would like for example
to see for yourself how your program handles a fixed input over varying column
widths, use something like
for ((M= 0; M <= maximumWidth; M++))
do python3 assn1P3.py $M < inputFile |
tail -1
done
in zsh (the “zed shell”). Other shells can do similar.
Each of the three .py-files you submit should still have in it the print
commands that you used for testing it yourself. That way I can test it too — if I
have to. (See http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF,
at the bottom of its p7.)
8.2 What will get marks — and what won’t
AAA 2
BBB 2
CCC 4
DDD 5
EEE 2
FFF 4
GGG 2
HHH 1
III 2
JJJ 2
KKK 6
LLL 4
MMM 3
NNN 3
OOO 2
PPP 2
QQQ 2
RRR 2
SSS
· · · ∼0
ZZZ
∑
50
Here’s how to get good marks: If you were asked to fill-in AAA and BBB for
the program
# N>=0
n,s= N,0
while AAA: # INV: s==sum(as[n:])
BBB
# s==sum(as)
and you answered
# Your name; your zID.
# N>=0
n,s= N,0
while n!=0: # INV: s==sum(as[n:])
n= n-1
s= s+as[n]
# s==sum(as)
then you would get full marks. Your answer need not be coloured; but the
replacements n!=0 and n= n-1; s=s+as[n] you gave for AAA and BBB have
respected the blue assertions that were there already.
12
Here’s how to lose marks: If on the other hand your answer had been this
program (which still does correctly calculate the sum, but. . . )
# N>=0
n,s= 0,0
while n!=N: # INV: s==sum(as[n:])
s,n= s+as[n],n+1
# s==sum(as)
then your marks would be considerably less than full. (And you would have
forgotten to put your name and zID in the file.) For even though the second
version “V2” satisfies the specification
{N>=0} V2 {s==sum(as)}
it does not respect the invariant s==sum(as[n:]) that was supplied.
In “real life” you could of course change the invariant to something that V2
does respect, 6 and indeed in the long run that’s the point: you must find your
own invariants. But in this assignment you are not allowed to change the
assertions — for learning to respect assertions or the invariants (ultimately,
your own or your team mates’) is what this assignment is practising.
Thus the program fragments you fill in must respect the blue assertions that
are supplied: if they don’t, you risk losing marks even if your program is correct.
Each three-letter question identifier from AAA up to RRR comes with a small
number indicating how many marks it is worth. Their total is 50, and your
mark will contribute 30% to the overall course mark.
The question identifiers from SSS to ZZZ have no mark indicators because
they are optional, just for those who are interested in looking further. Ex-
ceptionally good work there might, however, gain a few marks extra in the
assignment overall. But you can get 100% for fully correct work on AAA up to
RRR alone.
8.3 Frequently Asked Questions
• What happens if I change the assertions? If you think an assertion is
wrong, contact the lecturer (me) privately. If you change the assertion,
you risk losing marks.
• What happens if my program works but it does not respect the assertions?
If your program does not respect the assertions, then you cannot be sure
it is working. At best, your tests have established only that it probably
works. Any of your answers that don’t respect the assertions that surround
them risk receiving few marks.
• What happens if my program respects the assertions, but doesn’t work?
// Can’t happen. If your program respects the assertions,
then it must work. 7
6What would it be?
13
• What exactly must I hand in, by when, and how?
/ (1,2,3)You must hand in the files asked for in the marginal indications 1,2,3, that is
the three text-files
• assn1P1.py ,
• assn1P2.py and
• assn1P3.py .
(Obviously each file is likely to be a prefix of the following one; but submitting
them this way makes it easier to run them, should I want to.)
Submit your work using give, i.e. with
give cs6721 assn1 assn1P1.py assn1P2.py assn1P3.py .
Please be careful to use exactly the file names given, and to make
sure that your zID and name occurs within each separate file as a
Python comment).
The deadline is 18:00 (6pm) on Sunday 27 June 2021.
Late submissions will be mark-capped at 85% up to one day; for two days at
75%, three days at 65% and four days at 50%. After four days late, submissions
are no longer accepted.
9 Extras
There are no marks “officially” available for this section (or the next); thus
don’t attempt it unless you have completed the assignment proper.
Still, there could be a small benefit available if some of this is really well done.
9.1 Greedy typesetting
If you’d like to compare your program with the (much simpler) greedy approach,
you can change just your answer to Part (2) so that it fills lines greedily, ignoring
Part (1) — which you can leave just sitting there, since it does no harm.
What change would you make? SSS
9.2 Even spacing
In MakeLines the blanks are distributed over the line to make it exactly M
characters long. And some white-spaces will be longer than others. How would
you make all the longer white-spaces come before the shorter ones? TTT Or all
longer ones come after the shorter ones? UUU
What about making the distribution of longer and shorter white-spaces be
more or less even, as in Fig. 1? VVV
7 . . . unless of course the assertions themselves are wrong.
I have tried to make sure they are right.
14
9.3 Number of lines used
Minimising the white space has a disadvantage, that in some case more lines
might be used overall than for the greedy strategy. (Look ahead to Sec. 10.)
That is, a greedy strategy might not be pretty, but it probably does save paper.
Can you see why our current code doesn’t necessarily use as few lines as
possible, even though it minimises average white space? WWW
10 Epilogue: a quite long paragraph
As for Sec. 9, there are no marks “officially” available for this section; again,
don’t attempt it unless you have completed the assignment proper.
Still, there could be a small benefit available if some if this is really well done.
The text in the figures below comes from Kipling’s “How the Leopard got his
Spots”; 8 In Fig. 7 it is typeset with the greedy strategy, and a very ugly white
space occurs about half-way down (three 8-space blanks in a row), probably
caused by the very long word on the following line; but at least the fewest
possible number of lines (37) is used for the paragraph overall. Can you explain
rigorously why the greedy strategy is guaranteed to use the fewest lines? (Of
course it’s “obvious” — but can you be rigorous about it?) XXX
In Fig. 8 the code of this assignment is used, in particular Part (2). The
largest average space is only 3.33, and it indeed looks much better. But it does
take two more lines overall (39)
In Fig. 9 the code of this assignment is again used, except that Part (2) is
modified to take the longest line possible consistent with minimising the white
space (which, being minimum, must be the same as for Fig. 8). However it takes
fewer lines than Fig. 8 — though still more than Fig. 7. (See Sec. 9.3.)
Can you modify our Part (2) to achieve the effect of Fig. 9? Do it by
proposing a revised INV4 first — of course. YYY What’s your new loop? ZZZ
Carroll Morgan
9 June 2021
8Rudyard Kipling. Just so Stories, Macmillan, 1902.
15
IN the days when everybody started fair,
Best Beloved, the Leopard lived in a place
called the High Veldt. ’Member it wasn’t
the Low Veldt, or the Bush Veldt, or the
Sour Veldt, but the ’sclusively bare, hot,
shiny High Veldt, where there was sand and
sandy-coloured rock and ’sclusively tufts
of sandy-yellowish grass. The Giraffe and
the Zebra and the Eland and the Koodoo and
the Hartebeest lived there; and they were
’sclusively sandy-yellow-brownish all
over; but the Leopard, he was the
’sclusivest sandiest-yellowish-brownest of
them all-a greyish-yellowish catty-shaped
kind of beast, and he matched the
’sclusively yellowish-greyish-brownish
colour of the High Veldt to one hair. This
was very bad for the Giraffe and the Zebra
and the rest of them; for he would lie
down by a ’sclusively
yellowish-greyish-brownish stone or clump
of grass, and when the Giraffe or the
Zebra or the Eland or the Koodoo or the
Bush-Buck or the Bonte-Buck came by he
would surprise them out of their jumpsome
lives. He would indeed! And, also, there
was an Ethiopian with bows and arrows (a
’sclusively greyish-brownish-yellowish man
he was then), who lived on the High Veldt
with the Leopard; and the two used to hunt
together-the Ethiopian with his bows and
arrows, and the Leopard ’sclusively with
his teeth and claws-till the Giraffe and
the Eland and the Koodoo and the Quagga
and all the rest of them didn’t know which
way to jump, Best Beloved. They didn’t
indeed!
Line-width 42, cost 8, lines used 37.
Figure 7: Greedy strategy
IN the days when everybody started
fair, Best Beloved, the Leopard lived
in a place called the High Veldt.
’Member it wasn’t the Low Veldt,
or the Bush Veldt, or the Sour
Veldt, but the ’sclusively bare, hot,
shiny High Veldt, where there was sand
and sandy-coloured rock and ’sclusively
tufts of sandy-yellowish grass. The
Giraffe and the Zebra and the Eland
and the Koodoo and the Hartebeest
lived there; and they were ’sclusively
sandy-yellow-brownish all over; but
the Leopard, he was the ’sclusivest
sandiest-yellowish-brownest of them alla
greyish-yellowish catty-shaped kind of
beast, and he matched the ’sclusively
yellowish-greyish-brownish colour of the
High Veldt to one hair. This was very
bad for the Giraffe and the Zebra and
the rest of them; for he would lie down by
a ’sclusively yellowish-greyish-brownish
stone or clump of grass, and when
the Giraffe or the Zebra or the Eland
or the Koodoo or the Bush-Buck or
the Bonte-Buck came by he would surprise
them out of their jumpsome lives. He
would indeed! And, also, there was
an Ethiopian with bows and arrows (a
’sclusively greyish-brownish-yellowish man
he was then), who lived on the High Veldt
with the Leopard; and the two used to hunt
togetherthe Ethiopian with his bows and
arrows, and the Leopard ’sclusively with
his teeth and clawstill the Giraffe and
the Eland and the Koodoo and the Quagga
and all the rest of them didn’t know which
way to jump, Best Beloved. They didn’t
indeed!
Line-width 42, cost 3.33, lines used 39.
Figure 8: Clever strategy
IN the days when everybody started fair,
Best Beloved, the Leopard lived in a place
called the High Veldt. ’Member it wasn’t
the Low Veldt, or the Bush Veldt, or the
Sour Veldt, but the ’sclusively bare, hot,
shiny High Veldt, where there was sand and
sandy-coloured rock and ’sclusively
tufts of sandy-yellowish grass. The
Giraffe and the Zebra and the Eland
and the Koodoo and the Hartebeest
lived there; and they were ’sclusively
sandy-yellow-brownish all over; but
the Leopard, he was the ’sclusivest
sandiest-yellowish-brownest of them all-a
greyish-yellowish catty-shaped kind of
beast, and he matched the ’sclusively
yellowish-greyish-brownish colour of the
High Veldt to one hair. This was very bad
for the Giraffe and the Zebra and the rest
of them; for he would lie down by
a ’sclusively yellowish-greyish-brownish
stone or clump of grass, and when the
Giraffe or the Zebra or the Eland or
the Koodoo or the Bush-Buck or the
Bonte-Buck came by he would surprise
them out of their jumpsome lives. He
would indeed! And, also, there was
an Ethiopian with bows and arrows (a
’sclusively greyish-brownish-yellowish man
he was then), who lived on the High Veldt
with the Leopard; and the two used to hunt
together-the Ethiopian with his bows and
arrows, and the Leopard ’sclusively with
his teeth and claws-till the Giraffe and
the Eland and the Koodoo and the Quagga
and all the rest of them didn’t know which
way to jump, Best Beloved. They didn’t
indeed!
Line-width 42, cost 3.33, lines used 38.
Figure 9: Cleverer strategy