Algorithm Design with Haskell Richard S. Bird instant download
Algorithm Design with Haskell Richard S. Bird instant download
Bird
download
https://textbookfull.com/product/algorithm-design-with-haskell-
richard-s-bird/
https://textbookfull.com/product/thinking-functionally-with-
haskell-richard-bird/
https://textbookfull.com/product/the-algorithm-design-manual-3rd-
edition-steven-s-skiena/
https://textbookfull.com/product/bird-s-engineering-
mathematics-9th-edition-john-bird/
https://textbookfull.com/product/bird-s-higher-engineering-
mathematics-9th-edition-john-bird/
The Ethical Algorithm The Science of Socially Aware
Algorithm Design 6th Edition Kearns
https://textbookfull.com/product/the-ethical-algorithm-the-
science-of-socially-aware-algorithm-design-6th-edition-kearns/
https://textbookfull.com/product/bird-s-basic-engineering-
mathematics-8th-edition-john-o-bird/
https://textbookfull.com/product/haskell-the-ultimate-beginner-s-
guide-to-learn-haskell-programming-step-by-step-1st-edition-
claudia-alves/
https://textbookfull.com/product/thinking-with-types-type-level-
programming-in-haskell-sandy-maguire/
https://textbookfull.com/product/applied-computational-thinking-
with-python-algorithm-design-for-complex-real-world-problems-2nd-
edition-sofia-de-jesus-dayrene-martinez/
ALGORITHM DESIGN WITH HASKELL
This book is devoted to five main principles of algorithm design: divide and
conquer, greedy algorithms, thinning, dynamic programming, and exhaustive
search. These principles are presented using Haskell, a purely functional language,
leading to simpler explanations and shorter programs than would be obtained
with imperative languages. Carefully selected examples, both new and standard,
reveal the commonalities and highlight the differences between algorithms. The
algorithm developments use equational reasoning where applicable, clarifying the
applicability conditions and correctness arguments. Every chapter concludes with
exercises (nearly 300 in total), each with complete answers, allowing the reader to
consolidate their understanding and apply the techniques to a range of problems.
The book serves students (both undergraduate and postgraduate), researchers,
teachers, and professionals who want to know more about what goes into a good
algorithm and how such algorithms can be expressed in purely functional terms.
A L G O R I T H M D E S I G N W I T H HASKELL
RICHARD BIRD
University of Oxford
JEREMY GIBBONS
University of Oxford
University Printing House, Cambridge CB2 8BS, United Kingdom
One Liberty Plaza, 20th Floor, New York, NY 10006, USA
477 Williamstown Road, Port Melbourne, VIC 3207, Australia
314–321, 3rd Floor, Plot 3, Splendor Forum, Jasola District Centre, New Delhi – 110025, India
79 Anson Road, #06–04/06, Singapore 079906
www.cambridge.org
Information on this title: www.cambridge.org/9781108491617
DOI: 10.1017/9781108869041
© Richard Bird and Jeremy Gibbons 2020
This publication is in copyright. Subject to statutory exception
and to the provisions of relevant collective licensing agreements,
no reproduction of any part may take place without the written
permission of Cambridge University Press.
First published 2020
Printed in the United Kingdom by TJ International Ltd, Padstow Cornwall
A catalogue record for this publication is available from the British Library.
ISBN 978-1-108-49161-7 Hardback
Cambridge University Press has no responsibility for the persistence or accuracy of
URLs for external or third-party internet websites referred to in this publication
and does not guarantee that any content on such websites is, or will remain,
accurate or appropriate.
For Stephen Gill (RB) and Sue Gibbons (JG).
Contents
Index 432
Preface
that calculation is fun to do but boring to read – well, too much of it is. Although it
does not matter very much whether imperative algorithms are expressed in C or Java
or pseudo-code, the situation changes completely when algorithms are expressed
functionally.
Many of the problems considered in this book, especially in the later parts, begin
with a specification of the task in hand, expressed as a composition of standard
functions such as maps, filters, and folds, as well as other functions such as perms for
computing all the permutations of a list, parts for computing all the partitions, and
mktrees for building all the trees of a particular kind. These component functions
are then combined, or fused, in various ways to construct a final algorithm with the
required time complexity. A final sorting algorithm may not refer to the underlying
tree, but the tree is still there in the structure of the algorithm. The notion of fusion
dominates the technical and mathematical aspects of the design process and is really
the driving force of the book.
The disadvantage for any author of taking a functional approach is that, be-
cause functional languages such as Haskell are not so well known as mainstream
procedural languages, one has to spend some time explaining them. That would
add substantially to the length of the book. The simple solution to this problem
is just to assume the necessary knowledge. There is a growing range of textbooks
on languages like Haskell, including our own Thinking Functionally with Haskell
(Cambridge University Press, 2014), and we will just assume the reader is familiar
with the necessary material. Indeed, the present book was designed as a companion
volume to the earlier book. A brief summary of what we do assume, and an even
briefer reprise of some essential ideas, is given in the first chapter, but you will
probably not be able to learn enough about Haskell there to understand the rest
of the book. Even if you do know something about functional programming, but
not about how equational reasoning enters the picture (some books on functional
programming simply don’t mention equational reasoning), you will probably still
have to refer to our earlier book. In any case, the mathematics involved in equational
reasoning is neither new nor difficult.
Books on algorithm design traditionally cover three broad areas: a collection of
design principles, a study of useful data structures, and a number of interesting and
intriguing algorithms that have been discovered over the centuries. Sometimes the
books are arranged by principles, sometimes by topic (such as graph algorithms, or
text algorithms), and sometimes by a mixture of both. This book mostly takes the
first approach. It is devoted to five main design strategies underlying many effective
algorithms: divide and conquer, greedy algorithms, thinning algorithms, dynamic
programming, and exhaustive search. These are the design strategies that every
serious programmer should know. The middle strategy, on thinning algorithms,
is new, and serves in many problems as an alternative to dynamic programming.
Preface xv
Each design strategy is allocated a part to itself, and the chapters on each strategy
cover a variety of algorithms from the well-known to the new. There is only a
little material on data structures – only as much as we need. In the first part of the
book we do discuss some basic data structures, but we will also rely on some of
Haskell’s libraries of other useful ways of structuring data. One reason for doing so
is that we wanted the book not to be too voluminous; another reason is that there
does exist one text, Chris Okasaki’s Purely Functional Data Structures (Cambridge
University Press, 1998), that covers a lot of the material. Other books on functional
data structures have been published since we began writing this book, and more are
beginning to appear.
Another feature of this book is that, as well as some firm favourites, it describes a
number of algorithms that do not usually appear in books on algorithm design. Some
of these algorithms have been adapted, elaborated, and simplified from yet another
book published by Cambridge University Press: Pearls of Functional Algorithm
Design (2010). The reason for this novelty is simply to make the book entertaining
as well as instructive. Books on algorithm design are read, broadly speaking, by
three kinds of people: academics who need reference material, undergraduate or
graduate students on a course, and professional programmers simply for interest
and enjoyment. Most professional programmers do not design algorithms but just
take them from a library. Yet they too are a target audience for this book, because
sometimes professional programmers want to know more about what goes into a
good algorithm and how to think about them.
Algorithms in real life are a good deal more intricate than the ones presented in
this book. The shortest-path algorithm in a satellite navigation system is a good
deal more complicated than a shortest-path algorithm as presented in a textbook
on algorithm design. Real-life algorithms have to cope with the problems of scale,
with the effective use of a computer’s hardware, with user interfaces, and with many
other things that go into a well-designed and useful product. None of these aspects
is covered in the present book, nor indeed in most books devoted solely to the
principles of algorithm design.
There is another feature of this book that deserves mention: all exercises are
answered, if sometimes somewhat briefly. The exercises form an integral part of
the text, and the questions and answers should be read even if the exercises are not
attempted. Rather than have a complete bibliography at the end of the book, each
chapter ends with references to (some of) the books and articles pertinent to the
chapter.
Most of the major programs in this book are available on the web site
www.cs.ox.ac.uk/publications/books/adwh
xvi Preface
You can also use this site to see a list of all known errors, as well as report new ones.
We also welcome suggestions for improvement, including ideas for new exercises.
Acknowledgements
Preparation of this book has benefited enormously from careful reading by Sue
Gibbons, Hsiang-Shang Ko, and Nicolas Wu. The manuscript was prepared using
the lhs2TEX system of Ralf Hinze and Andres Löh, which pretty-prints the Haskell
code and also allows it to be extracted and type-checked. The extracted code was
then tested using the wonderful QuickCheck tool developed by Koen Claessen and
John Hughes. Type-checking and QuickChecking the code has saved us from many
infelicities; any errors that remain are, of course, our own responsibility.
We also thank David Tranah and the team at Cambridge University Press for their
advice and hard work in the generation of the final version of the text.
Richard Bird
Jeremy Gibbons
PART ONE
BASICS
3
What makes a good algorithm? There are as many answers to this question as there
are to the question of what makes a good cookbook recipe. Is the recipe clear and
easy to follow? Does the recipe use standard and well-understood techniques? Does
it use widely available ingredients? Is the preparation time reasonably short? Does it
involve many pots and pans and a lot of kitchen space? And so on and so on. Some
people when asked this question say that what is most important about a recipe
is whether the dish is attractive or not, a point we will try to bear in mind when
expressing our functional algorithms.
In the first three chapters we review the ingredients we need for designing good
recipes for attractive algorithms in a functional kitchen, and describe the tools we
need for analysing their efficiency. Our functional language of choice is Haskell,
and the ingredients are Haskell functions. These ingredients and the techniques for
combining them are reviewed in the first chapter. Be aware that the chapter is not
an introduction to Haskell; its main purpose is to outline what should be familiar
territory to the reader, or at least territory that the reader should feel comfortable
travelling in.
The second chapter concerns efficiency, specifically the running time of algo-
rithms. We will ignore completely the question of space efficiency, for the plain
fact of the matter is that executing a functional program can take up quite a lot of
kitchen space. There are methods for controlling the space used in evaluating a
functional expression, but we refer the reader to other books for their elaboration.
That chapter reviews asymptotic notation for stating running times, and explores
how recurrence relations, which are essentially recursive functions for determining
the running times of recursive functions, can be solved to give asymptotic estimates.
The chapter also introduces, albeit fairly briefly, the notion of amortised running
times because it will be needed later in the book.
The final chapter in this part introduces a small number of basic data structures
that will be needed at one or two places in the rest of the book. These are symmetric
lists, random-access lists, and purely functional arrays. Mostly we postpone discus-
sion of any data structure required to make an algorithm efficient until the algorithm
itself is introduced, but these three form a coherent group that can be discussed
without having specific applications in mind.
Chapter 1
Functional programming
Haskell is a large and powerful language, brimming with clever ideas about how
to structure programs and possessing many bells and whistles. But in this book we
will use only a small subset of the host of available features. So, no Monads, no
Applicatives, no Foldables, and no Traversables. In this chapter we will spell out
what we do need to construct effective algorithms. Some of the material will be
revisited when particular problems are put under the microscope, so you should
regard the chapter primarily as a way to check your understanding of the basic ideas
of Haskell.
silver, but not necessarily made in France, we have to write “a lovely little old
French-silver butter knife” to avoid ambiguity. Mathematical expressions too are
usually understood from right to left, certainly those involving a chain of functional
compositions. As to deceptiveness, the definition
head = foldr () ⊥ where x y = x
though a little strange is certainly correct and takes constant time. The evaluation
of foldr (), conceptually from right to left, is abandoned after the first element is
encountered. Thus
head (x : xs) = foldr () ⊥ (x : xs)
= x foldr () ⊥ xs
=x
The last step follows from the fact that Haskell is a lazy language in which eval-
uations are performed only when needed, so evaluation of does not require
evaluation of its second argument.
Sometimes the direction of travel is important. For example, consider the follow-
ing two definitions of concat:
concat1 , concat2 :: [[a]] → [a]
concat1 = foldr (++) [ ]
concat2 = foldl (++) [ ]
We have concat1 xss = concat2 xss for all finite lists xss (see Exercise 1.10), but
which definition is better? We will look at the precise running times of the two
functions in the following chapter, but here is one way to view the problem. Imagine
a long table on which there are a number of piles of documents. You have to assemble
these documents into one big pile ensuring that the correct order is maintained, so
the second pile (numbering from left to right) has to go under the first pile, the third
pile under the second pile, and so on. You could start from left to right, picking up
the first pile, putting it on top of the second pile, picking the combined pile up and
putting it on top of the third pile, and so on. Or you could start at the other end,
placing the penultimate pile on the last pile, the antepenultimate pile on top of that,
and so on (even English words are direction-biased: the words ‘first’, ‘second’, and
‘third’ are simple, but ‘penultimate’ and ‘antepenultimate’ are not). The left to right
solution involves some heavy lifting, particularly at the last step when a big pile
of documents has to be lifted up and placed on the last pile, but the right to left
solution involves picking up only one pile at each step. So concat1 is potentially a
much more efficient way to concatenate a list of lists than concat2 .
Here is another example. Consider the problem of breaking a list of words into
a list of lines, ensuring that the width of each line is at most some given bound.
This problem is known as the paragraph problem, and there is a section devoted
1.3 Inductive and recursive definitions 9
to it in Chapter 12. It seems natural to process the input from left to right, adding
successive words to the end of the current line until no more words will fit, in
which case a new line is started. This particular algorithm is a greedy one. There
are also non-greedy algorithms for the paragraph problem that process words from
right to left. Part Three of the book is devoted to the study of greedy algorithms.
Nevertheless, these two examples apart, the direction of travel is often unimportant.
The direction of travel is also related to another concept in algorithm design,
the notion of an online algorithm. An online algorithm is one that processes a list
without having the entire list available from the start. Instead, the list is regarded
as a potentially infinite stream of values. Consequently, any online algorithm for
solving a problem for a given stream also has to solve the problem for every prefix
of the stream. And that means the stream has to be processed from left to right. In
contrast, an offline algorithm is one that is given the complete list to start with, and
can process the list in any order it wants. Online algorithms can usually be defined
in terms of another basic Haskell function scanl, whose definition is as follows:
scanl :: (b → a → b) → b → [a] → [b]
scanl f e [ ] = [e]
scanl f e (x : xs) = e : scanl f (f e x) xs
For example,
scanl (⊕) e [x, y, z, ...] = [e, e ⊕ x, (e ⊕ x) ⊕ y, ((e ⊕ x) ⊕ y) ⊕ z, ...]
In particular, scanl can be applied to an infinite list, producing an infinite list as
result.
Expressing perms2 in this way rather than by a list comprehension helps with
equational reasoning, and also with the analysis of its running time. We will return
to both perms1 and perms2 in the following chapter.
The different styles, recursive or inductive, of the definitions of basic combinato-
rial functions, such as permutations, partitions, or subsequences, lead to different
kinds of final algorithm. For example, divide-and-conquer algorithms are usually
recursive, while greedy and thinning algorithms are usually inductive. To appreciate
that there may be different algorithms for one and the same problem, one has to go
back to the definitions of the basic functions used in the specification of the problem
and see if they can be defined differently. For example, the inductive definition of
perms1 leads to Insertion sort, while the recursive definition of perms2 leads to Se-
lection sort. These two sorting algorithms will be introduced in the context of greedy
algorithms in Part Three. The general point is a key one for functional algorithm
design: different solutions for problems arise simply because there are different
but equally clear definitions of one or more of the basic functions describing the
solution.
While functional programming relies solely on recursion to define arbitrarily long
computations, imperative programming can also make use of loops of various kinds,
including while and until loops. We can define and use loops in Haskell too. For
example,
until :: (a → Bool) → (a → a) → a → a
until p f x = if p x then x else until p f (f x)
is a recursive definition of the function until that repeatedly applies a function to a
value until the result satisfies some condition. We will encounter until again later in
the book. Given until we can define while by
while p = until (not · p)
We can also define a functional version of simple for-loops in which a function is
applied to an argument a specified number of times (see the exercises).
1.4 Fusion
The most powerful technique for constructing efficient algorithms lies in our ability
to fuse two computations together into one computation. Here are three simple
examples:
map f · map g = map (f · g)
concatMap f · map g = concatMap (f · g)
foldr f e · map g = foldr (f · g) e
12 Functional programming
The first equation says that the two-step process of applying one function to every
element of a list, and then applying a second function to every element of the
result, can be replaced by a one-step traversal in which the composition of the two
functions is applied to each element. The second equation is an instance of the first
one, and the third is yet another example of when two traversals can be replaced by
a single traversal.
Here is an another example of a fusion law, one for you to solve:
foldr f e · concat = ????
Pause for a minute or so to try and complete the right-hand side. It is a good test of
your understanding of the material so far. But do not be discouraged if you cannot
find the answer, because it is not too obvious and many experienced functional
programmers would fail to spot it. In a moment we will show how this particular
fusion rule follows from one single master rule. Indeed, that is how we ourselves
know the right-hand side, not by memorising it but by reconstructing it from the
master rule.
You probably paused for a short time, gave up and then read on. But you don’t
get away that easily. Try this simpler version first:
foldr f e (xs ++ ys) = ????
Having answered this question, can you now answer the first one?
The answers to both questions will be given shortly. The master fusion rule is the
fusion law of foldr. This law states that
h (foldr f e xs) = foldr g (h e) xs
for all finite lists xs provided
h (f x y) = g x (h y)
for all x and y. The proviso is called the fusion condition. Without one extra proviso,
the restriction to finite lists is necessary (see the exercises). The proof of the fusion
rule is by induction on the structure of a list. There are two cases, a base case and
an induction step. The base case is
h (foldr f e [ ])
= { definition of foldr }
he
= { definition of foldr }
foldr g (h e) [ ]
The induction step is
Another Random Scribd Document
with Unrelated Content
no end of adventures to tell you, but it is no place to take you and
your mother, and I don't want to leave you again."
"Oh, I'm so glad, we'll be near Uncle and Aunt Mildred," said
Jean.
"Not me?" asked Sandy mischievously.
"Oh, you, of course," said Jean. "We are going to be Australians
ourselves, now, and of course we won't forget our Little Australian
Cousin."
THE END.
FOOTNOTE:
[20] The Blacks can count only as high as their ten fingers.
Anything above this they call always "eighty-eight," though no
one knows why.
Being three "Little Colonel" stories in the Cosy Corner Series, "The
Little Colonel," "Two Little Knights of Kentucky," and "The Giant
Scissors," put into a single volume.
BIG BROTHER
Special Holiday Editions
Each one volume, cloth decorative, small quarto, $1.25
New plates, handsomely illustrated with eight
full-page drawings in color, and many marginal
sketches.
IN THE DESERT OF WAITING: The Legend of Camelback
Mountain.
THE THREE WEAVERS: A Fairy Tale for Fathers and Mothers
as Well as for Their Daughters.
KEEPING TRYST
THE LEGEND OF THE BLEEDING HEART
THE RESCUE OF PRINCESS WINSOME: A Fairy Play
for Old and Young.
SWEET NANCY
The Further Adventures of the Doctor's Little Girl. By Marion Ames
Taggart.
One vol., library, 12mo, illustrated $1.50
In the new book, the author tells how Nancy becomes in fact "the
doctor's assistant," and continues to shed happiness around her.
CARLOTA
A Story of the San Gabriel Mission. By Frances Margaret Fox.
Square 12mo, cloth decorative, illustrated and decorated in colors by
Ethelind Ridgway $1.00
"It is a pleasure to recommend this little story as an entertaining
contribution to juvenile literature."—The New York Sun.
PUSSY-CAT TOWN
By Marion Ames Taggart.
Small quarto, cloth decorative, illustrated and decorated in colors
$1.00
"Anything more interesting than the doings of the cats in this
story, their humor, their wisdom, their patriotism, would be hard to
imagine."—Chicago Post.
O-HEART-SAN
The Story of a Japanese Girl. By Helen Eggleston Haskell.
Small quarto, cloth decorative, illustrated and decorated in colors by
Frank P. Fairbanks $1.00
"The story comes straight from the heart of Japan. The shadow of
Fujiyama lies across it and from every page breathes the fragrance
of tea leaves, cherry blossoms and chrysanthemums."—The Chicago
Inter-Ocean.
Mildred's Inheritance.
A delightful little story of a lonely English girl who comes to
America and is befriended by a sympathetic American family who are
attracted by her beautiful speaking voice. By means of this one gift
she is enabled to help a school-girl who has temporarily lost the use
of her eyes, and thus finally her life becomes a busy, happy one.
Big Brother.
A story of two boys. The devotion and care of Steven, himself a
small boy, for his baby brother, is the theme of the simple tale.
By EDITH ROBINSON
Brother Billy.
The story of Betty's brother, and some further adventures of Betty
herself.
Adventures of a Brownie.
The story of a household elf who torments the cook and gardener,
but is a constant joy and delight to the children who love and trust
him.
By MARSHALL SAUNDERS
For His Country.
A sweet and graceful story of a little boy who loved his country;
written with that charm which has endeared Miss Saunders to hosts
of readers.
Down in Dixie.
A fascinating story for boys and girls, of a family of Alabama
children who move to Florida and grow up in the South.
By MARIAN W. WILDMAN
Loyalty Island.
An account of the adventures of four children and their pet dog
on an island, and how they cleared their brother from the suspicion
of dishonesty.
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
textbookfull.com