1 Introduction
This book started after teaching an intensive course on algorithms to working programmers in Kyiv, in spring 2016. It took more than 3 years to complete, and, meanwhile, I also did 3 iterations of the course. Its aim is to systematically explain how to write efficient programs and, also, the approaches and tools for determining why the program isn’t efficient enough. In the process, it will teach you some Lisp and show in action the technics of algorithmic development. And, even if you won’t program in Lisp afterwards, you’ll still be able to utilize the same approaches and tools, or be inclined to ask why they aren’t available in your language of choice, from its authors :)
Why Algorithms Matter
In our industry, currently, there seems to prevail a certain misunderstanding of the importance of algorithms for the working programmer. There’s often a disconnect between the algorithmic questions posed at the job interviews and the everyday essence of the same job. That’s why opinions are voiced that you, actually, don’t have to know CS to be successful in the software developer’s job. That’s true, you don’t, but you’d better do if you want to be in the notorious top 10% programmers. For several reasons. One is that, actually, you can find room for algorithms almost at every corner of your work — provided you are aware of their existence. To put it simply, the fact that you don’t know a more efficient or elegant solution to a particular programming problem doesn’t make your code less crappy. The current trend in software development is that, although the hardware becomes more performant, the software becomes slower faster. There are two reasons for that, in my humble opinion:
- Most of the application programmers don’t know the inner workings of the underlying platforms. And the number of platform layers keeps increasing.
- Most of the programmers also don’t know enough algorithms and algorithmic development technics to squeeze the most from their code. And often this means a loss of one or more orders of magnitude of performance.
In the book, I’ll address, primarily, the second issue but will also try to touch on the first whenever possible.
Besides, learning the art of solving difficult algorithmic problems trains the brain and makes it more apt to solving various other problems, in the course of your day-to-day work.
Finally, you will be speaking the same lingua franca as other advanced programmers — the tongue that transcends the mundane differences of particular programming languages. And you’ll gain a more detached view of those differences, freeing your mind from the dictate of a particular set of choices exhibiting in any one of them.
One of the reasons for this gap of understanding of the value of algorithms, probably, originates from how they are usually presented in the computer science curriculum. First, it is often done in a rather theoretical or “mathematical” way with rigorous proofs and lack of connection to the real world™. Second, the audience is usually freshmen or sophomores who don’t have a lot of practical programming experience and thus can’t appreciate and relate how this knowledge may be applied to their own programming challenges (because they didn’t have those yet) — rather, most of them are still at the level of struggling to learn well their first programming language and, in their understanding of computing, are very much tied to its choices and idiosyncrasies.
In this book, the emphasis is made on the demonstration of the use of the described data structures and algorithms in various areas of computer programming. Moreover, I anticipate that the self-selected audience will comprise programmers with some experience in the field. This makes a significant difference in the set of topics that are relevant and how they can be conveyed. Another thing that helps a lot is when the programmer has a good command of more than one programming language, especially, if the languages are from different paradigms: static and dynamic, object-oriented and functional. These factors allow bridging the gap between “theoretical” algorithms and practical coding, making the topic accessible, interesting, and inspiring.
This is one answer to a possible question: why write another book on algorithms? Indeed, there are several good textbooks and online courses on the topic, of which I’d recommend the most Steven Skienna’s The Algorithm Design Manual. Yet, as I said, this book is not at all academic in presentation of the material, which is a norm for other textbooks. Except for simple arithmetic, it contains almost no “math” or proofs. And, although proper attention is devoted to algorithm complexity, it doesn’t deal with theories of complexity or computation and similar scientific topics. Besides, all the algorithms and data structures come with some example practical use cases. Last, but not least, there’s no book on algorithms in Lisp, and, in my opinion, it’s a great topic to introduce the language. The next chapter will provide a crash course to grasp the basic ideas, and then we’ll discuss various Lisp programming approaches alongside the algorithms they will be used to implement.
This is an introductory book, not a bible of algorithms. It will draw a comprehensive picture and cover all topics necessary for further advancement of your algorithms knowledge. However, it won’t go too deep into the advanced topics, such as persistent or probabilistic data structures, advanced tree, graph, and optimization algorithms, as well as algorithms for particular fields, such as Machine Learning, Cryptography or Computational Geometry. All of those fields require (and usually have) separate books of their own.
A Few Words about Lisp
For a long time, I’ve been contemplating writing an introductory book on Lisp, but something didn’t add up, I couldn’t see the coherent picture, in my mind. And then I got a chance to teach algorithms with Lisp. From my point of view, it’s a perfect fit for demonstrating data structures and algorithms (with a caveat that students should be willing to learn it), while discussing the practical aspects of those algorithms allows to explain the language naturally. At the same time, this topic requires almost no endeavor into the adjacent areas of programming, such as architecture and program design, integration with other systems, user interface, and use of advanced language features, such as types or macros. And that is great because those topics are overkill for an introductory text and they are also addressed nicely and in great detail elsewhere (see Practical Common Lisp and ANSI Common Lisp).
Why Lisp is great for algorithmic programs? One reason is that the language was created with such use case in mind. It has support for all the proper basic data structures, such as arrays, hash-tables, linked lists, strings, and tuples. It also has a numeric tower, which means no overflow errors and, so, a much saner math. Next, it’s created for the interactive development style, so the experimentation cycle is very short, there’s no compile-wait-run-revise red tape, and there are no unnecessary constraints, like the need for additional annotations (a.k.a. types), prohibition of variable mutation or other stuff like that. You just write a function in the REPL, run it and see the results. In my experience, Lisp programs look almost like pseudocode. Compared to other languages, they may be slightly more verbose at times but are much more clear, simple, and directly compatible with the algorithm’s logical representation.
But why not choose a popular programming language? The short answer is that it wouldn’t have been optimal. There are 4 potential mainstream languages that could be considered for this book: C++, Java, Python, and JavaScript. (Surely, there’s already enough material on algorithms that uses them). The first two are statically-typed, which is, in itself, a big obstacle to using them as teaching languages. Java is also too verbose, while C++ — too low-level. These qualities don’t prevent them from being used in the majority of production algorithm code, in the wild, and you’ll, probably, end up dealing with such code sooner than later if not already. Besides, their standard libraries provide great examples of practical algorithm implementation. But, I believe that gaining good conceptual understanding will allow to easily adapt to one of these languages if necessary while learning them in parallel with diving into algorithms creates unnecessary complexity. Python and JS are, in many ways, the opposite choices: they are dynamic and provide some level of an interactive experience (albeit inferior compared to Lisp), but those languages are in many ways anti-algorithmic. Trying to be simple and accessible, they hide too much from the programmer and don’t give enough control of the concrete data. Teaching algorithms, using their standard libraries, seems like cheating to me as their basic data structures often are not what they claim to be. Lisp is in the middle: it is both highly interactive and gives enough control of the environment, while not being too verbose and demanding. And the price to pay — the unfamiliar syntax — is really small, in my humble opinion.
Mostly, this book will be dedicated to showing Lisp code and explaining it. Yet, all such code snippets will fall into two quite different categories:
- One kind will present complete (or almost complete with an exception of occasionally leaving small parts as excercises for you) and code blocks that could be run in the Lisp environment, accompanied with the examples of such invocations. These code blocks are accessible in a dedicated github repository.
- The other kind is represented by sketches used to explain how the presented algorithms will be built into larger systems (usually, you’ll see these sketches in the “In action” sections of each chapter). Such sketches will not be runnable as they may require a lot of supportng code and/or infrastructure, and should be treated only as an outline.