Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
b b
Categories and Subject Descriptors
D.1.1 [Programming Te
hniques℄: Appli
ative (Fun
- Laun
hbury knew of a solution by Jones and Gibbons [5℄
tional) Programming that depended on lazy evaluation, but wondered how one
would solve the problem in a stri
t language like Standard
ML [6℄. I qui
kly s
ribbled what seemed to me to be a
General Terms mostly straightforward answer and showed it to him at the
next break.
Algorithms, Design Over the next year, I presented the problem to many other
fun
tional programmers and was
ontinually amazed at the
Keywords baroque solutions I re
eived in reply. With only a single
ex
eption, everyone who
ame near a workable answer went
Breadth-rst numbering, breadth-rst traversal, views in a very dierent dire
tion from my solution right from the
very beginning of the design pro
ess. I gradually realized
that I was witnessing some sort of mass mental blo
k, a
1. INTRODUCTION
ommunal blind spot, that was steering programmers away
Breadth-rst traversal of a tree is easy, but rebuilding the from what seemed to be a very natural solution. I make no
tree afterwards seems to be mu
h harder, at least to fun
-
laims that mine is the best solution, but I nd it fas
inating
tional programmers. At ICFP'98, John Laun
hbury
hal- that something about my solution makes it so diÆ
ult for
lenged me with the following problem: fun
tional programmers to
on
eive of in the rst pla
e.
Given a tree T ,
reate a new tree of the same
shape, but with the values at the nodes repla
ed STOP!
by the numbers 1 : : : jT j in breadth-rst order.
Before reading further, spend ten or fteen minutes sket
h-
For example, breadth-rst numbering of the tree ing out a solution. For
on
reteness, assume that you have
a type of labeled binary trees
a
datatype 'a Tree = E
b d | T of 'a * 'a Tree * 'a Tree
b b b
and that you are to produ
e a fun
tion
b b
bfnum : 'a Tree -> int Tree
Permission to make digital or hard copies of all or part of this work for
2. BREADTH-FIRST TRAVERSAL
personal or classroom use is granted without fee provided that copies are When attempting to solve any non-trivial problem, the
not made or distributed for profit or commercial advantage and that copies rst step should always be to review solutions to related
bear this notice and the full citation on the first page. To copy otherwise, to problems. In algorithm design, as in programming in gen-
republish, to post on servers or to redistribute to lists, requires prior specific
permission and/or a fee.
eral, theft of ideas is to be applauded rather than
on-
ICFP’00, Montreal, Canada demned. In this
ase, the most obvious
andidate for plun-
Copyright 2000 ACM 1-58113-202-6/00/0009 ..$5.00 der is the well-known queue-based algorithm for breadth-rst
traversal, that is, produ
ing a list of the labels in a tree, in
breadth-rst order [5℄. For example, breadth-rst traversal signature QUEUE =
of the tree sig
a
type 'a Queue
val empty : 'a Queue
b d val isEmpty : 'a Queue -> bool
b
b b
b b
val enq : 'a Queue * 'a -> 'a Queue
val deq : 'a Queue -> 'a * 'a Queue
should yield the list [a; b; d;
℄. end
The key step in developing an algorithm for breadth-rst
traversal is to generalize the problem, illustrating the para- signature BFTRAV =
doxi
al, yet
ommon, phenomenon that a more general prob- sig
lem is often easier to solve. In parti
ular, we generalize the val bftrav : 'a Tree -> 'a list
problem from breadth-rst traversal of a tree to breadth- end
rst traversal of a forest, that is, from
bftrav : 'a Tree -> 'a list fun
tor BreadthFirstTraversal (Q:QUEUE) : BFTRAV =
stru
t
to open Q
bftrav' : 'a Tree Seq -> 'a list
fun bftrav' q =
where Seq is some as-yet-undetermined type of sequen
es if isEmpty q then [℄
used to represent forests. For example, breadth-rst traver- else
ase deq q of
sal of the forest (E, ts) => bftrav' ts
| (T (x,a,b), ts) =>
a d e x :: bftrav' (enq (enq (ts,a),b))
b
b b b f
b b b b b b
fun bftrav t = bftrav' (enq (empty, t))
end
should yield the list [a; d; e; b;
; f ℄.
Then, bftrav
an be spe
ied by the equation
bftrav t = bftrav' hti Figure 1: Breadth-rst traversal in SML.
where hti denotes the singleton forest
ontaining t.
Now, bftrav' is easy to spe
ify with the following three
equations:
bftrav' h i = [℄ signature QUEUE =
bftrav' (E ts) = bftrav' ts sig
bftrav' (T (x,a,b) ts) = type 'a Queue
x :: bftrav' (ts a b) val empty : 'a Queue
val >> : 'a Queue * 'a -> 'a Queue
The last equation takes the
hildren of the rst tree and
adds them to the end of the sequen
e. The empty sequen
e viewtype 'a Queue = Empty | << of 'a * 'a Queue
is denoted h i, and the symbols and denote inx \
ons" end
and \sno
", respe
tively. Sin
e this is a spe
i
ation rather
than an implementation, I feel free to use h i, , and on fun
tor BreadthFirstTraversal (Q:QUEUE) : BFTRAV =
both sides of the equations. stru
t
The nal step before a
tually produ
ing
ode is to
hoose open Q
an implementation for the sequen
e ADT. The main opera- infix >>
tions we need on these sequen
es are adding trees to the end infixr <<
of the sequen
e and removing trees from the beginning of
the sequen
e. Therefore, we
hoose queues as our sequen
e fun bftrav' Empty = [℄
representation. Figure 1 gives a
on
rete implementation in | bftrav' (E << ts) = bftrav' ts
Standard ML. | bftrav' (T (x,a,b) << ts) =
The use of queues as an ADT makes this
ode look rather x :: bftrav' (ts >> a >> b)
ugly to an eye a
ustomed to the
leanliness of pattern
mat
hing, espe
ially the if-then-else and
ase expressions fun bftrav t = bftrav' (empty >> t)
in bftrav'. The problem is that pattern mat
hing
annot end
normally be performed on ADTs. Views [10℄ oer a way
around this problem. Figure 2 reimplements breadth-rst
traversal more
leanly using the syntax for views proposed Figure 2: Breadth-rst traversal using views.
in [9℄. Note that the denition of bftrav' is now nearly
identi
al to the spe
i
ation.
Provided ea
h queue operation runs in O(1) time, this
algorithm runs in O(n) time altogether. A good implemen-
tation of queues for this appli
ation would be the usual im-
plementation as a pair of lists [1, 2, 3℄. Sin
e this appli
ation signature BFNUM =
does not require persisten
e, fan
ier kinds of queues (e.g., [3, sig
7℄) would be overkill. val bfnum : 'a Tree -> int Tree
end
3. BREADTH-FIRST NUMBERING
We next attempt to extend the solution to breadth-rst fun
tor BreadthFirstNumbering (Q:QUEUE) : BFNUM =
traversal to get a solution to breadth-rst numbering. As stru
t
in breadth-rst traversal, we will begin by generalizing the open Q
problem. Instead of breadth-rst numbering of a tree, we
will
onsider breadth-rst numbering of a forest. In other fun bfnum' i q =
words, we introdu
e a helper fun
tion that takes a forest and if isEmpty q then empty
returns a numbered forest of the same shape. It will also be else
ase deq q of
helpful for the helper fun
tion to take the
urrent index, so (E, ts) => enq (bfnum' i ts, E)
its signature will be | (T (x,a,b), ts) =>
let val q = enq (enq (ts, a), b)
bfnum' : int -> 'a Tree Seq -> int Tree Seq val q' = bfnum' (i+1) q
Then bfnum
an be spe
ied in terms of bfnum' as val (b', q'') = deq q'
val (a', ts') = deq q''
bfnum t = t' in enq (ts', T (i,a',b')) end
where ht'i = bfnum' 1 hti
Extending the equations for bftrav' to bfnum' is fairly fun bfnum t =
straightforward, remembering that the output forest must let val q = enq (empty, t)
always have the same shape as the input forest. val q' = bfnum' 1 q
val (t',_) = deq q'
bfnum' i h i = h i in t' end
bfnum' i (E ts) = E ts' end
where ts' = bfnum' i ts
bfnum' i (T (x,a,b) ts) = T (i,a',b') ts'
where ts' a' b' = bfnum' (i+1) (ts a b) Figure 3: Breadth-rst numbering in SML.
Noti
e how every equation textually preserves the shape of
the forest.
Given these spe
i
ations, we need to
hoose a representa-
tion for sequen
es. The main operations we need on forests
are adding and removing trees at both the front and the
ba
k. Therefore, we
ould
hoose double-ended queues as
our sequen
e representation (perhaps using Hoogerwoord's
implementation of double-ended queues [4℄). However, a fun
tor BreadthFirstNumbering (Q:QUEUE) : BFNUM =
loser inspe
tion reveals that we treat the input forest and stru
t
the output forest dierently. In parti
ular, we add trees to open Q
the ba
k of input forests and remove them from the front, infixr <<
whereas we add trees to the front of output forests and re- infix >>
move them from the ba
k. If we remove the arti
ial
on-
straint that input forests and output forests should be rep- fun bfnum' i Empty = empty
resented with the same kind of sequen
e, then we
an repre- | bfnum' i (E << ts) = bfnum' i ts >> E
sent input forests as ordinary queues and output forests as | bfnum' i (T (x,a,b) << ts) =
ba
kwards queues. let val b' << a' << ts' =
If we want to represent both input forests and output bfnum' (i+1) (ts >> a >> b)
forests as ordinary queues (perhaps be
ause our library doesn't in ts' >> T (i,a',b') end
in
lude ba
kwards queues), then we
an
hange the spe
i-
ation of bfnum' to return the numbered forest in reverse fun bfnum t =
order. Then, the equations be
ome let val t' << Empty = bfnum' 1 (empty >> t)
bfnum' i h i = h i in t' end
bfnum' i (E ts) = ts' E end
where ts' = bfnum' i ts
bfnum' i (T (x,a,b) ts) = ts' T (i,a',b')
where b' a' ts' = bfnum' (i+1) (ts a b) Figure 4: Breadth-rst numbering using views.
Now it is a simple matter to turn this spe
i
ation into
running
ode, either with views (Figure 4) or without (Fig-
ure 3). Either way, assuming ea
h queue operation runs in
O (1) time, the entire algorithm runs in O(n) time. On
e stru
ture BreadthFirstNumberingByLevels : BFNUM =
again, the usual implementation of queues as a pair of lists stru
t
would be a good
hoi
e for this algorithm. fun
hildren E = [℄
|
hildren (T (x,a,b)) = [a,b℄
4. LEVEL-ORIENTED SOLUTIONS fun rebuild i [℄ [℄ = [℄
Nearly all the alternative solutions I re
eived from other | rebuild i (E :: ts)
s = E :: rebuild i ts
s
fun
tional programmers are level oriented, meaning that they | rebuild i (T (_,_,_) :: ts) (a :: b ::
s) =
expli
itly pro
ess the tree (or forest) level by level. In
on- T (i,a,b) :: rebuild (i+1) ts
s
trast, my queue-based solutions do not make expli
it the
transition from one level to the next. The main advantage of fun bfnum' i [℄ = [℄
the level-oriented approa
h is that it relies only on lists, not | bfnum' i lvl =
on fan
ier data stru
tures su
h as queues or double-ended let val nextLvl =
on
at (map
hildren lvl)
queues. val j = i + (length nextLvl div 2)
I will not attempt to des
ribe all the possible level-oriented val nextLvl' = bfnum' j nextLvl
solutions. Instead, to provide a fair
omparison to my queue- in rebuild i lvl nextLvl' end
based approa
h, I will des
ribe only the
leanest of these de-
signs. (For
ompleteness, I also review Jones and Gibbons' fun bfnum t = hd (bfnum' 1 [t℄)
algorithm in Appendix A, but their algorithm is not dire
tly end
omparable to mine sin
e it depends on lazy evaluation.)
Given a list of trees, where the roots of those trees form
the
urrent level, we
an extra
t the next level by
olle
ting Figure 5: Level-oriented breadth-rst numbering.
the subtrees of any non-empty nodes in the
urrent level, as
in
to prefer one over the other. The level-oriented solution
on
at (map
hildren lvl) is perhaps slightly easier to design from s
rat
h, but the
queue-based algorithm is only a modest extension of the
where queue-based algorithm for breadth-rst traversal, whi
h is
fun
hildren E = [℄
quite well-known (more well-known, in fa
t, than the level-
|
hildren (T (x,a,b)) = [a,b℄
oriented algorithm for breadth-rst traversal). Informal tim-
ings indi
ate that the level-oriented solution to breadth-rst
Later, after a re
ursive
all has numbered all the trees in numbering is slightly faster than the queue-based one, but
the next level, we
an number the
urrent level by walking the dieren
e is minor and is not in any
ase an a priori
down both lists simultaneously, taking two numbered trees justi
ation for favoring the level-oriented approa
h.
from the next level for every non-empty node in the
urrent Why is it then that fun
tional programmers fa
ed with
level. this problem so overwhelmingly
ommit to a level-oriented
approa
h right from the beginning of the design pro
ess?
fun rebuild i [℄ [℄ = [℄ I
an only spe
ulate, armed with ane
dotal responses from
| rebuild i (E :: ts)
s = E :: rebuild i ts
s those programmers who have attempted the exer
ise. I have
| rebuild i (T (_,_,_) :: ts) (a :: b ::
s) = identied four potential explanations:
T (i,a,b) :: rebuild (i+1) ts
s
Unfamiliarity with the underlying traversal algorithm.
The last tri
ky point is how to
ompute the starting index A programmer unfamiliar with the queue-based algo-
for numbering the next level from the starting index for the rithm for breadth-rst traversal would be ex
eedingly
urrent level. We
annot simply add the length of the list unlikely to
ome up with the queue-based algorithm
representing the
urrent level to the
urrent index, be
ause for breadth-rst numbering. However, this a
ounts
the
urrent level may
ontain arbitrarily many empty nodes, for only a small fra
tion of parti
ipants in the exer-
whi
h should not in
rease the index. Instead, we need to
ise.
nd the number of non-empty nodes in the
urrent level.
Although we
ould dene a
ustom fun
tion to
ompute that Unfamiliarity with fun
tional queues and double-ended
value, we
an instead noti
e that ea
h non-empty node in queues. A programmer unfamiliar with the fa
t that
the
urrent level
ontributes two nodes to the next level, and su
h data stru
tures
an be implemented fun
tionally
therefore merely divide the length of the next level by two. would be unlikely to design an algorithm that required
The
omplete algorithm appears in Figure 5. their use. In this
ategory, I perhaps have an un-
This algorithm makes three passes over ea
h level, rst fair advantage, having invented a variety of new im-
omputing its length, then
olle
ting its
hildren, and nally plementations of fun
tional queues and double-ended
rebuilding the level. At the pri
e of slightly messier
ode, we queues [8℄. But most programmers profess an aware-
ould easily
ombine the rst two passes, but there seems ness that these data stru
tures are available o-the-
to be no way to a
omplish all three tasks in a single pass shelf, even if they
ouldn't say ohand how those im-
without lazy evaluation. plementations worked.
Premature
ommitment to a data stru
ture. Most fun
-
5. DISCUSSION tional programmers immediately rea
h for lists, and
Comparing my queue-based solution with the level-oriented try something fan
ier only if they get stu
k. Even the
solution in the previous se
tion, I see no
ompelling reason programmer who initially
hooses queues is likely to
run into trouble be
ause of the opposite orientations
of the input and output queues. The queue-based algo- - jQ -
QQ
1 1 2