The Adelphi University Mod of Think Python ports to Javascript the Learning with Python 3 (RLE) edition by Peter Wentworth.
Copyright Notice
Copyright (C) 2018. Matthew X. Curinga.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with Invariant Sections being Foreword, Preface, and Contributor List, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled “GNU Free Documentation License”.
In the Ed Tech department at Adelphi, we have switched our introductory programming courses from Python to Javascript. Javascript has many of the features we like about Python, but also offers easy congiguration and a path to developing web and mobile apps without introducing additional programming languages. Most of the text is a straight port of the Python text, with some details to the specific Javascript features. The chapters are augmented with video tutorials and large code samples that focus on unstructured problem solving.
By David Beazley
As an educator, researcher, and book author, I am delighted to see the completion of this book. Python is a fun and extremely easy-to-use programming language that has steadily gained in popularity over the last few years. Developed over ten years ago by Guido van Rossum, Python’s simple syntax and overall feel is largely derived from ABC, a teaching language that was developed in the 1980’s. However, Python was also created to solve real problems and it borrows a wide variety of features from programming languages such as C++, Java, Modula-3, and Scheme. Because of this, one of Python’s most remarkable features is its broad appeal to professional software developers, scientists, researchers, artists, and educators.
Despite Python’s appeal to many different communities, you may still wonder why Python? or why teach programming with Python? Answering these questions is no simple task—especially when popular opinion is on the side of more masochistic alternatives such as C++ and Java. However, I think the most direct answer is that programming in Python is simply a lot of fun and more productive.
When I teach computer science courses, I want to cover important concepts in addition to making the material interesting and engaging to students. Unfortunately, there is a tendency for introductory programming courses to focus far too much attention on mathematical abstraction and for students to become frustrated with annoying problems related to low-level details of syntax, compilation, and the enforcement of seemingly arcane rules. Although such abstraction and formalism is important to professional software engineers and students who plan to continue their study of computer science, taking such an approach in an introductory course mostly succeeds in making computer science boring. When I teach a course, I don’t want to have a room of uninspired students. I would much rather see them trying to solve interesting problems by exploring different ideas, taking unconventional approaches, breaking the rules, and learning from their mistakes. In doing so, I don’t want to waste half of the semester trying to sort out obscure syntax problems, unintelligible compiler error messages, or the several hundred ways that a program might generate a general protection fault.
One of the reasons why I like Python is that it provides a really nice balance between the practical and the conceptual. Since Python is interpreted, beginners can pick up the language and start doing neat things almost immediately without getting lost in the problems of compilation and linking. Furthermore, Python comes with a large library of modules that can be used to do all sorts of tasks ranging from web-programming to graphics. Having such a practical focus is a great way to engage students and it allows them to complete significant projects. However, Python can also serve as an excellent foundation for introducing important computer science concepts. Since Python fully supports procedures and classes, students can be gradually introduced to topics such as procedural abstraction, data structures, and object-oriented programming — all of which are applicable to later courses on Java or C++. Python even borrows a number of features from functional programming languages and can be used to introduce concepts that would be covered in more detail in courses on Scheme and Lisp.
In reading Jeffrey’s preface, I am struck by his comments that Python allowed him to see a higher level of success and a lower level of frustration and that he was able to move faster with better results. Although these comments refer to his introductory course, I sometimes use Python for these exact same reasons in advanced graduate level computer science courses at the University of Chicago. In these courses, I am constantly faced with the daunting task of covering a lot of difficult course material in a blistering nine week quarter. Although it is certainly possible for me to inflict a lot of pain and suffering by using a language like C++, I have often found this approach to be counterproductive—especially when the course is about a topic unrelated to just programming. I find that using Python allows me to better focus on the actual topic at hand while allowing students to complete substantial class projects.
Although Python is still a young and evolving language, I believe that it has a bright future in education. This book is an important step in that direction. David Beazley University of Chicago Author of the Python Essential Reference
To paraphrase the philosophy of the Free Software Foundation, this book is free like free speech, but not necessarily free like free pizza. It came about because of a collaboration that would not have been possible without the GNU Free Documentation License. So we would like to thank the Free Software Foundation for developing this license and, of course, making it available to us.
We would also like to thank the more than 100 sharp-eyed and thoughtful readers who have sent us suggestions and corrections over the past few years. In the spirit of free software, we decided to express our gratitude in the form of a contributor list. Unfortunately, this list is not complete, but we are doing our best to keep it up to date. It was also getting too large to include everyone who sends in a typo or two. You have our gratitude, and you have the personal satisfaction of making a book you found useful better for you and everyone else who uses it. New additions to the list for the 2nd edition will be those who have made on-going contributions.
If you have a chance to look through the list, you should realize that each person here has spared you and all subsequent readers from the confusion of a technical error or a less-than-transparent explanation, just by sending us a note.
Impossible as it may seem after so many corrections, there may still be errors in this book. If you should stumble across one, we hope you will take a minute to contact us. The email address (for the Python 3 version of the book) is p.wentworth@ru.ac.za. Substantial changes made due to your suggestions will add you to the next version of the contributor list (unless you ask to be omitted). Thank you!
horsebet.py
, which was used as a case study in an earlier version of the book. Their program can now be found on the website.catTwice
function in Section 3.10.increment
function in Chapter 13.The goal of this book is to teach you to think like a computer scientist. This way of thinking combines some of the best features of mathematics, engineering, and natural science. Like mathematicians, computer scientists use formal languages to denote ideas (specifically computations). Like engineers, they design things, assembling components into systems and evaluating tradeoffs among alternatives. Like scientists, they observe the behavior of complex systems, form hypotheses, and test predictions.
The single most important skill for a computer scientist is problem solving. Problem solving means the ability to formulate problems, think creatively about solutions, and express a solution clearly and accurately. As it turns out, the process of learning to program is an excellent opportunity to practice problem-solving skills. That’s why this chapter is called, The way of the program.
On one level, you will be learning to program, a useful skill by itself. On another level, you will use programming as a means to an end. As we go along, that end will become clearer.
The programming language you will be learning is Javascript. Javascript is an example of a high-level language; other high-level languages you might have heard of are Python, C++, PHP, Pascal, C#, and Java. It’s important to point out that Java and Javascript are two different programming languages with different syntax, styles, purposes, and histories. They unfortunately, share a very close name!
As you might infer from the name high-level language, there are also low-level languages, sometimes referred to as machine languages or assembly languages. Loosely speaking, computers can only execute programs written in low-level languages. Thus, programs written in a high-level language have to be translated into something more suitable before they can run.
Almost all programs are written in high-level languages because of their advantages. It is much easier to program in a high-level language so programs take less time to write, they are shorter and easier to read, and they are more likely to be correct. Second, high-level languages are portable, meaning that they can run on different kinds of computers with few or no modifications.
The engine that translates and runs Javascript is called the Javascript Interpreter. All modern web browser (Firefox, Chrome, Safari, MS Edge/IE, etc) ship with a Javascript interpreter. You can also install a stand-alone interpreter on your computer.
The longer code examples in this book will use Repl.it, an online programming platform and community. Code listings include links to the “repl” that you can copy (“fork”) and experiment with.
There are two ways to use the interpreter: interactive or immediate mode and script mode. In immediate mode, you type Javascript expressions into the interpreter’s console, and the interpreter immediately shows the result. This is the an example of the repl.it interpreter’s console:
The 2+2
is entered at the Javascript prompt. The ⠕
token indicates a statement entered at the prompt. The interpreter uses the prompt to indicate that it is ready for instructions. We typed 2 + 2
and hit enter
the interpreter evaluated our expression, and replied 4
, and on the next line it gave a new prompt. In this case, 4
is the output and it is indicated as output in our console by the =>
token.
Alternatively, you can write a program in a file and use the interpreter to execute the contents of the file. Such a file is called a script. Scripts have the advantage that they can be saved to disk, printed, shared, and so on.
For example, we created a file named main.js
using our text editor. By convention, files that contain Javascript programs have names that end with .js
, but they are just plain-text files.
To execute the program in repl.it, we simply click the “run” button. The output is shown in the console.
Working directly in the interpreter is convenient for testing short bits of code because you get immediate feedback. Think of it as scratch paper used to help you work out problems. Anything longer than a few lines should be put into a script.
A program is a sequence of instructions that specifies how to perform a computation. The computation might be something mathematical, such as solving a system of equations or finding the roots of a polynomial, but it can also be a symbolic computation, such as searching and replacing text in a document or (strangely enough) compiling a program.
The details look different in different languages, but a few basic instructions appear in just about every language:
Get data from the keyboard, a file, or some other device.
Display data on the screen or send data to a file or other device.
Perform basic mathematical operations like addition and multiplication.
Check for certain conditions and execute the appropriate sequence of statements.
Perform some action repeatedly, usually with some variation.
Believe it or not, that’s pretty much all there is to it. Every program you’ve ever used, no matter how complicated, is made up of instructions that look more or less like these. Thus, we can describe programming as the process of breaking a large, complex task into smaller and smaller subtasks until the subtasks are simple enough to be performed with sequences of these basic instructions.
That may be a little vague, but we will come back to this topic later when we talk about algorithms.
Programming is a complex process, and because it is done by human beings, it often leads to errors. Programming errors are called bugs and the process of tracking them down and correcting them is called debugging. Use of the term bug to describe small engineering difficulties dates back to at least 1889, when Thomas Edison had a bug with his phonograph.
Three kinds of errors can occur in a program: syntax errors, runtime errors, and semantic errors. It is useful to distinguish between them in order to track them down more quickly.
Javascript can only execute a program if the program is syntactically correct; otherwise, the process fails and returns an error message. Syntax refers to the structure of a program and the rules about that structure. For example, in English, a sentence must begin with a capital letter and end with a period. this sentence contains a syntax error. So does this one
For most readers, a few syntax errors are not a significant problem, which is why we can read the poetry of E. E. Cummings without (too many) problems. Javascript is not so forgiving. If there is a single syntax error anywhere in your program, Javascript will display an error message and quit, and you will not be able to run your program. During the first few weeks of your programming career, you will probably spend a lot of time tracking down syntax errors. As you gain experience, though, you will make fewer errors and find them faster.
The second type of error is a runtime error, so called because the error does not appear until you run the program. These errors are also called exceptions because they usually indicate that something exceptional (and bad) has happened.
Runtime errors are rare in the simple programs you will see in the first few chapters, so it might be a while before you encounter one.
The third type of error is the semantic error. If there is a semantic error in your program, it will run successfully, in the sense that the computer will not generate any error messages, but it will not do the right thing. It will do something else. Specifically, it will do what you told it to do.
The problem is that the program you wrote is not the program you wanted to write. The meaning of the program (its semantics) is wrong. Identifying semantic errors can be tricky because it requires you to work backward by looking at the output of the program and trying to figure out what it is doing.
One of the most important skills you will acquire is debugging. Although it can be frustrating, debugging is one of the most intellectually rich, challenging, and interesting parts of programming.
In some ways, debugging is like detective work. You are confronted with clues, and you have to infer the processes and events that led to the results you see.
Debugging is also like an experimental science. Once you have an idea what is going wrong, you modify your program and try again. If your hypothesis was correct, then you can predict the result of the modification, and you take a step closer to a working program. If your hypothesis was wrong, you have to come up with a new one. As Sherlock Holmes pointed out, When you have eliminated the impossible, whatever remains, however improbable, must be the truth. (A. Conan Doyle, The Sign of Four)
For some people, programming and debugging are the same thing. That is, programming is the process of gradually debugging a program until it does what you want. The idea is that you should start with a program that does something and make small modifications, debugging them as you go, so that you always have a working program.
For example, Linux is an operating system kernel that contains millions of lines of code, but it started out as a simple program Linus Torvalds used to explore the Intel 80386 chip. According to Larry Greenfield, one of Linus’s earlier projects was a program that would switch between displaying AAAA and BBBB. This later evolved to Linux (The Linux Users’ Guide Beta Version 1).
Later chapters will make more suggestions about debugging and other programming practices.
Natural languages are the languages that people speak, such as English, Spanish, and French. They were not designed by people (although people try to impose some order on them); they evolved naturally.
Formal languages are languages that are designed by people for specific applications. For example, the notation that mathematicians use is a formal language that is particularly good at denoting relationships among numbers and symbols. Chemists use a formal language to represent the chemical structure of molecules. And most importantly:
Programming languages are formal languages that have been designed to express computations.
Formal languages tend to have strict rules about syntax. For example, 3+3=6
is a syntactically correct mathematical statement, but 3=+6$
is not. H2O is a syntactically correct chemical name, but 2Zz is not.
Syntax rules come in two flavors, pertaining to tokens and structure. Tokens are the basic elements of the language, such as words, numbers, parentheses, commas, and so on. In Javascript, a statement like console.log("Happy New Year for ", 2013)
has 6 tokens: a function name, an open parenthesis (round bracket), a string, a comma, a number, and a close parenthesis.
It is possible to make errors in the way one constructs tokens.
One of the problems with 3=+6$
is that $
is not a legal token in mathematics (at least as far as we know). Similarly, 2Zz is not a legal token in chemistry notation because there is no element with the abbreviation Zz
.
The second type of syntax rule pertains to the structure of a statement—that is, the way the tokens are arranged. The statement 3=+6$
is structurally illegal because you can’t place a plus sign immediately after an equal sign. Similarly, molecular formulas have to have subscripts after the element name, not before. And in our Javascript example, if we omitted the comma, or if we changed the two parentheses around to say console.log)"Happy New Year for ",2013(
our statement would still have six legal and valid tokens, but the structure is illegal.
When you read a sentence in English or a statement in a formal language, you have to figure out what the structure of the sentence is (although in a natural language you do this subconsciously). This process is called parsing.
For example, when you hear the sentence, “The other shoe fell”, you understand that the other shoe is the subject and fell is the verb. Once you have parsed a sentence, you can figure out what it means, or the semantics of the sentence. Assuming that you know what a shoe is and what it means to fall, you will understand the general implication of this sentence.
Although formal and natural languages have many features in common—tokens, structure, syntax, and semantics—there are many differences:
Natural languages are full of ambiguity, which people deal with by using contextual clues and other information. Formal languages are designed to be nearly or completely unambiguous, which means that any statement has exactly one meaning, regardless of context.
In order to make up for ambiguity and reduce misunderstandings, natural languages employ lots of redundancy. As a result, they are often verbose. Formal languages are less redundant and more concise.
Formal languages mean exactly what they say. On the other hand, natural languages are full of idiom and metaphor. If someone says, “The other shoe fell”, there is probably no shoe and nothing falling.
You’ll need to find the original joke to understand the idiomatic meaning of the other shoe falling. Yahoo! Answers thinks it knows!
People who grow up speaking a natural language—everyone—often have a hard time adjusting to formal languages. In some ways, the difference between formal and natural language is like the difference between poetry and prose, but more so:
Words are used for their sounds as well as for their meaning, and the whole poem together creates an effect or emotional response. Ambiguity is not only common but often deliberate.
The literal meaning of words is more important, and the structure contributes more meaning. Prose is more amenable to analysis than poetry but still often ambiguous.
The meaning of a computer program is unambiguous and literal, and can be understood entirely by analysis of the tokens and structure.
Here are some suggestions for reading programs (and other formal languages). First, remember that formal languages are much more dense than natural languages, so it takes longer to read them. Also, the structure is very important, so it is usually not a good idea to read from top to bottom, left to right. Instead, learn to parse the program in your head, identifying the tokens and interpreting the structure. Finally, the details matter. Little things like spelling errors and bad punctuation, which you can get away with in natural languages, can make a big difference in a formal language.
Traditionally, the first program written in a new language is called Hello, World! because all it does is display the words, Hello, World! In Javascript, the script looks like this: (For scripts, we’ll show line numbers to the left of the Javascript statements.)
This is an example of using console.log
, Javascript’s print function, which doesn’t actually print anything on paper. It displays a value on the screen’s console. In this case, the result shown is
The quotation marks in the program mark the beginning and end of the value; they don’t appear in the result.
Some people judge the quality of a programming language by the simplicity of the Hello, World! program. By this standard, Javascript does about as well as possible.
In the late 1960s, Seymor Papert’s group at MIT introduced LOGO Turtle as a way to teach computer programming to kids. After learning the basic turtle commands (also called an API), you can make surprising computer graphics programs with only a little bit of code.
We’re using a version of Turtle that has been written for Javascript. Thanks to Morgan McGuire at Casual Effects for the code..
As we’ve seen, a program and algorithms consist of a number of commands or statements that execute in an order described by the program. There are many libraries or APIs (application programming interface) that help you useful do things in Javascript. Some APIsread files over a network, others choose random numbers, while others might encrypt data to make it more secure. The turtle
API has a number of commands that you can use to move a turtle around a screen and to draw shapes and patterns.
Here’s a short turtle graphics program.
// set up the "pen" color and width
setWidth(30);
setColor("red");
// move forward 100px to draw a red line
fd(100);
wait(1);
// raise the pen "pen up -- pu()", then return the turtle
// to Y coordinate 0 and move it to X coordinate 100
pu();
setY(0);
setX(100)
pd();
wait(1);
// draw a blue line
setColor("blue");
fd(100);
wait(1);
// move turtle to the top left quadrant
pu();
setY(300);
setX(-300)
pd();
// draw a "DeepPink" circle
setColor("DeepPink");
startFill("DeepPink");
arc(360, 80);
endFill();
// hide the turtle
ht();
Turtle programs use the metaphor of a turtle moving around drawing with a pen. To move the turtle without drawing, you call the “pen up” method, or pu()
, as we do on lines 12 and 26. The turtle is plotted on an x,y plane (also called a Cartesian Plane). The x
coordinate specifies the horizontal position of the turtle and the y
coordinate specifies the vertical position of the turtle. In this plane, (0, 0) is in the middle of the window. Negative x
coordinates are to the left of the center, and negative y
coordinates are below the center. We call the setX()
and setY()
methods to move the turtle’s position. If the pen is down, it draws while we move. If it’s up, turtle moves without a trace. Notice how we draw a “DeepPink” circle, starting on line 32? Because our Javascript program executes in a web browser, we can use any of the “named colors” that are part of the web development standard. You can find all of the name colors here.
To fully use the turtle API, you will need to read and understand the documentation. Part of becoming a programmer and thinking like a computer scientist includes the ability to read (and write!) technical documentation. No programmer remembers every possible language feature or available commands. Before you begin the exercises and lab for this chapter, review the full
fd(distance)
Move forward the given distance.
bk(distance)
Move backward the given distance.
rt(angle)
Turn right (clockwise) in place.
rt(angle, radius)
Turn right (clockwise) in an arc of the given radius.
lt(angle)
Turn left (counterclockwise) in place.
lt(angle, radius)
Turn left (counterclockwise) in an arc of the given radius.
arc(angle, radius)
Draw an arc around the turtle, without moving the turtle. The angle is relative to the current heading.
pu()
Pick the pen up to temporarily move without drawing.
pd()
Put the pen down to resume drawing.
ht()
Hide the turtle.
setColor(color)
Set the pen color by name such as RED or CSS color string such as “#FF0041”.
setColor(r, g, b)
Set the pen color based on three RGB values each between zero and one.
setColor(r, g, b, a)
Set the pen color based on three RGB values and an opacity value, each between zero and one.
startFill(color)
Start drawing a filled region of the given color. Must end with endFill()
startFill(r, g, b)
Start drawing a filled region with color given by three RGB values, each between zero and one.
setColor(r, g, b, a)
Start drawing a region filled by three RGB values and an opacity value, each between zero and one.
endFill()
End drawing a filled region and actually fill it. If the pen is down, then the outline will also be stroked.
setPosition(x, y)
Sets the absolute position. If the pen is down, draws a line to that position.
setX(x)
Sets the absolute x-axis position. If the pen is down, draws a line to that position.
getX()
Returns the absolute x-axis position.
setY(x)
Sets the absolute y-axis position. If the pen is down, draws a line to that position.
getY()
Returns the absolute y-axis position.
setWidth(width)
Sets the pen width.
setHeading(degrees)
Sets the current heading in degrees measured clockwise from the upwards vertical axis. North = 0, East = 90, South = 180, West = 270.
getHeading()
Returns the current heading in degrees measured clockwise from the upwards vertical axis. North = 0, East = 90, South = 180, West = 270.
setScale(s)
Scales all distances (but not x and y coordinates or pen width) by this factor. Useful for reusing drawing commands for different size objects. 1.0 is the default scale.
getScale()
Returns the current drawing scale.
setSpeed(speed)
Sets the number of commands executed before showing the next frame of animation. Defaults to 1. Can be set to Infinity to draw the entire image at once. Does not affect wait times.
clear(color)
Clears the screen to the specified color
clear(r, g, b)
Clears the screen to the specified color
wait(seconds)
Pauses drawing for approximately this many seconds. Useful for creating animations. Not affected by setSpeed.
turtle
to draw a red square with a pink borderOur first lab presents an open-ended exercise, just to get you started. Use turtle graphics to draw a picture. We suggest you spend about one hour working on this lab. Make sure that you includes some commands from the documentation that are not in the example program. Other than that, the content of your drawing is up to you.
A set of specific steps for solving a category of problems.
An error in a program.
Information in a program that is meant for other programmers (or anyone reading the source code) and has no effect on the execution of the program.
The process of finding and removing any of the three kinds of programming errors.
Another name for a runtime error.
Any one of the languages that people have designed for specific purposes, such as representing mathematical ideas or computer programs; all programming languages are formal languages.
A programming language like Javascript that is designed to be easy for humans to read and write.
A style of using Javascript where we type expressions at the command prompt, and the results are shown immediately. Contrast with script, and see the entry under Javascript shell.
The engine that executes your Javascript scripts or expressions.
A programming language that is designed to be easy for a computer to execute; also called machine language or assembly language.
Any one of the languages that people speak that evolved naturally.
The output of the compiler after it translates the program.
To examine a program and analyze the syntactic structure.
A property of a program that can run on more than one kind of computer.
A function used in a program or script that causes the Javascript interpreter to display a value on its output device.
The process of formulating a problem, finding a solution, and expressing the solution.
a sequence of instructions that specifies to a computer actions and computations to be performed.
An interactive user interface to the Javascript interpreter. The user of a Javascript shell types commands at the prompt, and presses the return key to send these commands immediately to the interpreter for processing.
An error that does not occur until the program has started to execute but that prevents the program from continuing.
A program stored in a file (usually one that will be interpreted).
An error in a program that makes it do something other than what the programmer intended.
The meaning of a program.
A program in a high-level language before being compiled.
The structure of a program.
An error in a program that makes it impossible to parse — and therefore impossible to interpret.
One of the basic elements of the syntactic structure of a program, analogous to a word in a natural language.
A value is one of the fundamental things — like a letter or a number — that a program manipulates. Programming turtle
we have been using values like 100
when we write fd(100)
and "blue"
in statements like setColor("blue")
.
These values are classified into different classes, or data types: 100
is an number, and "blue"
is a string, so-called because it contains a string of letters. You (and the interpreter) can identify strings because they are enclosed in quotation marks.
If you are not sure what class a value falls into, the Javascript typeof operator can tell you.
Not surprisingly, strings are of type string
and integers are of type number
. In Javascript, both whole numbers and fractions (numbers with decimal points) are of type number
. At this stage, you can treat the words class and type interchangeably. We’ll come back to a deeper understanding of what a class is in later chapters.
What about values like "17"
and "3.2"
? They look like numbers, but they are in quotation marks like strings.
They’re strings!
Strings in Javascript can be enclosed in either single quotes ('
) or double quotes ("
), or “backticks” ( `).
⠕ typeof 'This is a string.';
=> 'string'
⠕ typeof "And so is this.";
=> 'string'
⠕ typeof `and this is a special type of string...`;
=> 'string'
Double quoted strings can contain single quotes inside them, as in "Bruce's beard"
, and single quoted strings can have double quotes inside them, as in 'The knights who say "Ni!"'
.
Strings enclosed with the backtick symbol are called template literals. Template literals can contain either single or double quotes:
console.log(`"Oh no", she exclaimed, "Ben's bike is broken!"`);
"Oh no", she exclaimed, "Ben's bike is broken!"
⠕
Template literal strings can even span multiple lines:
⠕ let message = `This message will
... span several
... lines`;
console.log(message);
This message will
span several
lines.
Strings can also be joined use the +
token which adds strings together (also called concatenation).
⠕ let message = "This long message will" +
⠕ "... will appear on one line" +
⠕ "... when it's logged to the console.";
console.log(message);
This long message will... will appear on one line... when it's logged to the console.
Javascript doesn’t care whether you use single or double quotes to surround your strings: once it has parsed the text of your program or command, the way it stores the value is identical in all cases, and the surrounding quotes are not part of the value. But when the interpreter wants to display a string, it has to decide which quotes to use to make it look like a string. It usually choose a single quote.
⠕ 'This is a string.'
=> 'This is a string.'
⠕ "And so is this."
=> 'And so is this.'
⠕ `This is a string too!`
=> 'This is a string too!'
The code examples in this book will use double quotes as the default, and use single quotes and template literals when they make more sense (such as in strings containing double quotes or spanning multiple lines). We will learn more about template strings in the chapter on strings.
When you type a large number, you might be tempted to use commas between groups of three digits, as in 42,000
. This is not a legal number in Javascript, and different Javascript interpreters handle it differently. Formal languages are strict, the notation is concise, and even the smallest change might mean something quite different from what you intended.
One of the most powerful features of a programming language is the ability to manipulate variables. A variable is a name that refers to a value.
The let keyword declares a new variable and the assignment token, gives a value to a variable:
This example declares three variables and assigns them values. The first assigns the string value "What's up, Doc?"
to a variable named message
. The second gives the number 17
to n
, and the third assigns the number 3.14159
to a variable called pi
.
The assignment token, =
, should not be confused with equals, which uses the token ===
. The assignment statement binds a name, on the left-hand side of the operator, to a value, on the right-hand side. This is why you will get an error if you enter:
Tip: When reading or writing code, say to yourself “n is assigned 17” or “n gets the value 17”. Don’t say “n equals 17”.
A common way to represent variables on paper is to write the name with an arrow pointing to the variable’s value. This kind of figure is called a state snapshot because it shows what state each of the variables is in at a particular instant in time. (Think of it as the variable’s state of mind). This diagram shows the result of executing the assignment statements:
If you ask the interpreter to evaluate a variable, it will produce the value that is currently linked to the variable:
We use variables in a program to “remember” things, perhaps the current score in the video game. But variables are variable. This means they can change over time, just like the score in a video game. You can assign a value to a variable, and later assign a different value to the same variable.
(This is different from maths. In maths, if you give x
the value 3, it cannot change to link to a different value half-way through your calculations!)
⠕ let day = "Thursday"
⠕ day
=> 'Thursday'
⠕ day = "Friday"
⠕ day
=> 'Friday'
⠕ day = 21
⠕ day
=> 21
You’ll notice we changed the value of day
three times, and on the third assignment we even made it refer to a value that was of a different type.
A great deal of programming is about having the computer remember things, e.g. The number of missed calls on your phone, and then arranging to update or change the variable when you miss another call.
Variable names and other identifiers can be arbitrarily long. They can contain both letters and digits, but they have to begin with a letter, the dollar sign $
, or an underscore _
. Remember that case matters. Bruce
and bruce
are different variables.
In Javascript, capital letters are often used in variables that contain multiple words, such as myName
or thePriceOfTeaInChina
. The underscore character (_
) can appear in a name, too. You may see underscores used to separate multiple words, such as my_name
or price_of_tea_in_china
, but this style is less common in Javascript.
Sometimes programmers start variables with underscores or dollar signs to give them special meanings. In this book, all variable names will start with letters.
If you give a variable an illegal name, you get a syntax error:
⠕ let 76trombones = "big parade";
unknown: Identifier directly after number
⠕ let typeof = "Computer Science 101";
unknown: Unexpected token
76trombones
is illegal because it does not begin with a letter, but what’s wrong with typeof
?
It turns out that typeof
is reserved as one of Javascript’s keywords. Keywords define the language’s syntax rules and structure, and they cannot be used as variable names.
Javascript has almost forty keywords (different versions of Javascript have slightly different keywords):
await finally protected
break for return
case function super
catch if switch
class implements this
const import throw
continue in try
debugger instanceof typeof
default interface var
delete let void
else new while
export package with
extends private yield
You might want to keep this list handy. If the interpreter complains about one of your variable names and you don’t know why, see if it is on this list.
Programmers generally choose names for their variables that are meaningful to the human readers of the program—they help the programmer document, or remember, what the variable is used for.
A statement is an instruction that the Javascript interpreter can execute. We have mostly seen the assignment statement so far. Some other kinds of statements that we’ll see shortly are while
statements, for
statements, and if
statements. (There are other kinds too!)
When you type a statement in the console and hit enter, Javascript executes it. Statements don’t produce any result.
An expression is a combination of values, variables, operators, and calls to functions. If you type an expression at the Javascript prompt, the interpreter evaluates it and displays the result:
In this example typeof
is a Javascript operator that returns the type of a variable or literal data operand.
The evaluation of an expression produces a value, which is why expressions can appear on the right hand side of assignment statements. A value all by itself is a simple expression, and so is a variable.
These examples show the expression and the value they return indicated by =>
.
Operators are special tokens that represent computations like addition, multiplication and division. The values the operator uses are called operands.
The following are all legal Javascript expressions whose meaning is more or less clear:
20+32 hour-1 hour*60+minute minute/60 5**2 (5+9) * (15-7)
The tokens +
, -
, and *
, and the use of parenthesis for grouping, mean in Javascript what they mean in mathematics. The asterisk (*
) is the token for multiplication, and **
is the token for exponentiation.
When a variable name appears in the place of an operand, it is replaced with its value before the operation is performed.
Addition, subtraction, multiplication, and exponentiation all do what you expect.
Example: so let us convert 645 minutes into hours:
Oops! In Javascript, the division operator /
always yields a floating point (decimal) result. What we might have wanted to know was how many whole hours there are, and how many minutes remain. Javascript provides helpful Math functions to allow us to do this. Math.floor()
rounds a number down to the nearest whole number. Its result is always a whole number — and if it has to adjust the number it always moves it to the left on the number line. So Math.floor(6 / 4)
yields 1
, but Math.floor(-6 / 4)
might surprise you!
Here we’ll look at some ways to convert data. We call these type converters.
The Number.parseInt(arg)
function can take a floating point number or a string, and turn it into an whole number. For floating point numbers, it discards the decimal portion of the number — a process we call truncation towards zero on the number line. Let us see this in action:
⠕ Number.parseInt(3.14);
=> 3
⠕ Number.parseInt(3.9999); // This doesn't round to the closest int!
=> 3
⠕ Number.parseInt(3.0);
=> 3
⠕ Number.parseInt(-3.999); // Note that the result is closer to zero
=> -3
⠕ Number.parseInt(minutes / 60);
=> 10
⠕ Number.parseInt("2345"); // Parse a string to produce an int
=> 2345
⠕ Number.parseInt(17); // It even works if arg is already an int
=> 17
The type converter Number.parseFloat(arg)
can a syntactically legal string into a decimal:
The type converter String(arg)
turns its argument into a string:
When more than one operator appears in an expression, the order of evaluation depends on the rules of precedence. Javascript follows the same precedence rules for its mathematical operators that mathematics does. The acronym PEMDAS is a useful way to remember the order of operations:
2 * (3-1)
is 4, and (1+1)**(5-2)
is
(minute * 100) / 60
, even though it doesn’t change the result.2**1+1
is 3 and not 4, and 3*1**3
is 3 and not 27.2*3-1
yields 5 rather than 4, and 5-2*2
is 1, not 6.Operators with the same precedence are evaluated from left-to-right. In algebra we say they are left-associative. So in the expression 6-3+2
, the subtraction happens first, yielding 3. We then add 2 to get the result 5. If the operations had been evaluated from right to left, the result would have been 6-(3+2)
, which is 1. (The acronym PEDMAS could mislead you to thinking that division has higher precedence than multiplication, and addition is done ahead of subtraction - don’t be misled.
Subtraction and addition are at the same precedence, and the left-to-right rule applies.)
**
, so a useful hint is to always use parentheses to force exactly the order you want when exponentiation is involved:⠕ 2 ** 3 ** 2 // The right-most ** operator gets done first!
=> 512
⠕ (2 ** 3) ** 2 // Use parentheses to force the order you want!
=> 64
The Javascript console in repl.it is great for exploring and experimenting with expressions like this. You can fork this repl to try it: https://repl.it/@mcuringa/ES6-shell
If a string looks like a number, Javascript while try to automatically convert it to an number in order to execute a mathematical operation.
If the automatic type conversion fails in a mathematical operation, Javascript returns the special Not a Number value, NaN
.
Interestingly, the +
operator work with strings, but for strings, the +
operator represents concatenation, not addition.
As we’ve seen, concatenation means joining the two operands by linking them end-to-end. For example:
The output of this program is banana nut bread
. The space before the word nut
is part of the string, and is necessary to produce the space between the concatenated strings.
Automatic conversion can be tricky and sometimes lead to unexpected results. Consider:
There is a built-in function in Javascript for getting input from the user:
If you run this sample in a repl.it prompt, it will open a dialog window with the message:
Please enter your name:
The user of the program can type the name and hit enter
. When this happens the text that has been entered is returned from the prompt
function, and in this case assigned to the variable n
.
Even if you asked the user to enter their age, you would get back a string like "17"
. It would be your job, as the programmer, to convert that string into a integer or float before using it, if that was required.
So far, we have looked at the elements of a program — variables, expressions, statements, and function calls — in isolation, without talking about how to combine them.
One of the most useful features of programming languages is their ability to take small building blocks and compose them into larger chunks.
For example, we know how to get the user to enter some input, we know how to convert the string we get into a number, we know how to write a complex expression, and we know how to print values. Let’s put these together in a small four-step program that asks the user to input a value for the radius of a circle, and then computes the area of the circle from the formula
Firstly, we’ll do the four steps one at a time:
let response = window.prompt("What is your radius? ");
let r = Number.parseFloat(response);
let area = 3.14159 * r**2;
console.log("The area is ", area);
Now let’s compose the first two lines into a single line of code, and compose the second two lines into another line of code.
let r = Number.parseFloat(window.prompt("What is your radius? "));
console.log("The area is ", 3.14159 * r**2);
If we really wanted to be tricky, we could write it all in one statement:
console.log("The area is ", 3.14159 * Number.parseFloat(window.prompt("What is your radius? "))**2);
Such compact code may not be the most understandable for humans, but it does illustrate how we can compose bigger chunks from our building blocks.
If you’re ever in doubt about whether to compose code or fragment it into smaller steps, try to make it as simple as you can for the human to follow. My choice would be the first case above, with four separate steps.
The modulus operator works on integers (and integer expressions) and gives the remainder when the first number is divided by the second. In Javascript, the modulus operator is a percent sign (%
). The syntax is the same as for other operators. It has the same precedence as the multiplication operator.
So 7 divided by 3 is 2 with a remainder of 1.
The modulus operator turns out to be surprisingly useful. For example, you can check whether one number is divisible by another—if x % y
is zero, then x
is evenly divisible by y
.
Also, you can extract the right-most digit or digits from a number. For example, x % 10
yields the right-most digit of x
(in base 10). Similarly x % 100
yields the last two digits.
It is also extremely useful for doing conversions, say from seconds, to hours, minutes and seconds. So let’s write a program to ask the user to enter some seconds, and we’ll convert them into hours, minutes, and remaining seconds.
⠕ let totalSecs = Number.parseInt(window.prompt("How many seconds, in total?"));
⠕ let hours = Math.floor(totalSecs / 3600);
⠕ let secsStillRemaining = totalSecs % 3600;
⠕ let minutes = Math.floor(secsStillRemaining / 60);
⠕ let secsFinallyRemaining = secsStillRemaining % 60;
⠕
⠕ console.log("Hrs=", hours, " mins=", minutes, "secs=", secsFinallyRemaining);
To complete these exercises, open up a Javascript interpreter in repl.it. You can test things out in the right-side interactive window, and write your solutions in the Javascript source file on the left side. You might want to comment out the code you’re not working on, so that the output is less confusing.
Fork the standard ES6 Shell with window.prompt()
or the node.js shell with the terminal-only prompt()
function
console.log()
.6 * 1 - 2
to change its value from 4 to -6.Start the Javascript interpreter and enter bruce + 4
at the prompt. This will give you an error: ReferenceError: bruce is not defined
bruce
so that bruce + 4
evaluates to 10
.The formula for computing the final amount if one is earning compound interest is given on Wikipedia as
Write a Javascript program that assigns the principal amount of $10000 to variable P
, assign to n
the value 12, and assign to r
the interest rate of 8%. Set variable t
to be the number of years the money will be compounded for. Calculate and print the final amount after t
years.
Evaluate the following numerical expressions in your head, then use the Javascript interpreter to check your results:
⠕ 5 % 2
⠕ 9 % 5
⠕ 15 % 12
⠕ 12 % 15
⠕ 6 % 6
⠕ 0 % 7
⠕ 7 % 0
What happened with the last example? Why? If you were able to correctly anticipate the computer’s response in all but the last one, it is time to move on. If not, take time now to make up examples of your own. Explore the modulus operator until you are confident you understand how it works.
You look at the clock and it is exactly 2pm. You set an alarm to go off in 51 hours. At what time does the alarm go off? (Hint: you could count on your fingers, but this is not what we’re after. If you are tempted to count on your fingers, change the 51 to 5100.)
Write a Javascript program to solve the general version of the above problem. Create a variable hrs
to represent the number of hours to wait.
Your program should output what the time will be on the clock when the alarm goes off, regardless of the value that hrs
holds.
A statement that assigns a value to a name (variable). To the left of the assignment operator, =
, is a name. To the right of the assignment token is an expression which is evaluated by the Javascript interpreter and then assigned to the name. The difference between the left and right hand sides of the assignment statement is often confusing to new programmers. In the following assignment:
n = n + 1
n
plays a very different role on each side of the =
. On the right it is a value and makes up part of the expression which will be evaluated by the Javascript interpreter before assigning it to the name on the left.
=
is Javascript’s assignment token. Do not confuse it with equals, which is an operator for comparing values.
The ability to combine simple expressions and statements into compound statements and expressions in order to represent complex computations concisely.
To join two strings end-to-end.
A set of values. The type of a value determines how it can be used in expressions. So far, the types you have seen are number
and string
.
To simplify an expression by performing the operations in order to yield a single value.
A combination of variables, operators, and values that represents a single result value.
Javascript data type which stores floating-point numbers. Although integers and floats are are all of type number
, floating-point numbers are stored internally in two parts: a base and an exponent. When printed in the standard format, they look like decimal numbers. Beware of rounding errors when you use float
s, and remember that they are only approximate values.
An integer or whole number. In Javascript, its type is number
.
A reserved word that is used by the compiler to parse program; you cannot use keywords like if
, function
, and while
as variable names.
An operator, denoted with a percent sign ( %
), that works on integers and yields the remainder when one number is divided by another.
One of the values on which an operator operates.
A special symbol that represents a simple computation like addition, multiplication, or string concatenation.
The set of rules governing the order in which expressions involving multiple operators and operands are evaluated.
A graphical representation of a set of variables and the values to which they refer, taken at a particular instant during the program’s execution.
An instruction that the Javascript interpreter can execute. So far we have mostly seen the assignment statement.
A Javascript data type that holds a string of characters (e.g. textual data).
A number or string (or other things to be named later) that can be stored in a variable or computed in an expression.
A name that refers to a value.
A name given to a variable. Variable names in Javascript consist of a sequence of letters (a..z, A..Z, $, and _) and digits (0..9) that begins with a letter. In best programming practice, variable names should be chosen so that they describe their use in the program, making the program self documenting.
In Javascript, a function is a named sequence of statements that belong together. Their primary purpose is to help us organize programs into chunks that match how we think about the problem.
The syntax for a function definition is:
We can make up any names we want for the functions we create, except that we can’t use a name that is a Javascript keyword, and the names must follow the rules for legal identifiers (the same rules that apply to variable names).
There can be any number of statements inside the function, but they have to be between the curly braces ({}
). These statements make up the function body. In the examples in this book, we will use the standard indentation of two spaces. Function definitions are the second of several compound statements we will see, all of which have the same pattern:
We’ve already seen the for
loop which follows this pattern.
So looking again at the function definition, the keyword in the header is function
, which is followed by the name of the function and some parameters enclosed in parentheses. The parameter list may be empty, or it may contain any number of parameters separated from one another by commas. In either case, the parentheses are required. The parameters specifies what information, if any, we have to provide in order to use the new function.
Suppose we are writing a program to calculate the amount of tip due on a bill. We might write a function to “calculate tip”. “calculate tip” is an abstraction, or a mental chunk, of a number of smaller steps. So let’s write a function to capture the pattern of this “building block”:
/*
Calculate the tip on a bill, given the pct of the tip.
*/
function calculateTip (bill, pct) {
let tip = bill * (pct * .01); // convert pct to a decimal and calculate
tip = tip.toFixed(tip, 2); // convert tip to a number with 2 decimal places
total = tip + bill;
total = Number.parseFloat(total).toFixed(2);
// now show the results to the user
console.log("Bill amount: $" + bill);
console.log("Tip percentage: " + pct + "%");
console.log("Total amount due: $" + total);
}
// find the amount of an 18% tip on a $100 bill
calculateTip(100,18);
This function is named calculateTip
. It has two parameters: one to tell the function the amount of the bill, and the other to tell it the percent tip to calculate.
Defining a new function does not make the function run. To do that we need a function call. We’ve already seen how to call some built-in functions like console.log, window.input, and Number.parseInt. Function calls contain the name of the function being executed followed by a list of values, called arguments, which are assigned to the parameters in the function definition. So in the last line of the example program above, we call the function, and pass 100
as the amount of the bill and 18
as the percentage of the tip. While the function is executing, then, the variable bill
refers to the value 100, and the variable pct
refers to 18. We can pass either variables (like myBill
) or literal values (like 100
) as arguments.
Once we’ve defined a function, we can call it as often as we like, and its statements will be executed each time we call it. In the next example, we calculate 3 different tip amounts for the same bill, using calculateTip
defined above.
So far, we have looked at the elements of a program—variables
, expressions
, and statements
—in isolation, without talking about how to combine them.
One of the most useful features of programming languages is their ability to take small building blocks and compose them. In our calculateTip
example, we call several Javascript built-in functions: toFixed
to keep our amounts to 2 decimal places and Number.parseFloat
to convert the data to a float so that we can use toFixed
. We use console.log
to print our output on the Javascript console. As we will see, we can compose our programs of many functions that we define ourselves.
There are some points worth noting here:
calculateTip(myBill, 15)
. The parameters of this function, bill
and tip
, are assigned the values of the myBill variable, and the number literal 15, respectively.So far, it may not be clear why it is worth the trouble to create all of these new functions. Actually, there are a lot of reasons, but this example demonstrates two:
As we might expect, we have to create a function before we can execute it. In other words, the function definition has to be executed before the function is called.
In order to ensure that a function is defined before its first use, we have to know the order in which statements are executed, which is called the flow of execution.
Execution always begins at the first statement of the program. Statements are executed one at a time, in order from top to bottom.
Function definitions do not alter the flow of execution of the program, but remember that statements inside the function are not executed until the function is called. In Javascript we can define one function inside another. In this case, the inner definition isn’t executed until the outer function is called.
Function calls are like a detour in the flow of execution. Instead of going to the next statement, the flow jumps to the first line of the called function, executes all the statements there, and then comes back to pick up where it left off.
That sounds simple enough, until we remember that one function can call another. While in the middle of one function, the program might have to execute the statements in another function. But while executing that new function, the program might have to execute yet another function!
Fortunately, Javascript is adept at keeping track of where it is, so each time a function completes, the program picks up where it left off in the function that called it. When it gets to the end of the program, it terminates.
What’s the moral of this sordid tale? When we read a program, don’t read from top to bottom. Instead, follow the flow of execution.
Most functions require arguments: the arguments provide for generalization, allowing the same function to work with different data inputs. For example, if we want to find the absolute value of a number, we have to indicate what the number is. The Javascript Math
class has a built-in function for computing the absolute value:
In this example, the arguments to the abs
function are 5 and -5.
Some functions take more than one argument. For example the function calculateTip
function we wrote in the example above takes two arguments: the amount of the bill and the percent tip to calculate. Inside the function, the values that are passed get assigned to variables called parameters.
Another built-in function that takes more than one argument is Math.max
.
⠕ Math.max(7, 11);
=> 11
⠕ Math.max(4, 1, 17, 2, 12);
=> 17
⠕ Math.max(3 * 11, 5**3, 512 - 9, 1024**0);
=> 503
Math.max
can be passed any number of arguments, separated by commas, and will return the largest value passed. The arguments can be either simple values or expressions. In the last example, 503 is returned, since it is larger than 33, 125, and 1. All of the expressions are resolved — in this case the mathematical operations are calculated — before their values are assigned to the function parameters.
Some functions return values. In the previous section we saw that Math.abs
and Math.max
return values. calculateTip
does not return a value; it uses console.log
to produce output on the screen for the user. We can use the return values from functions to compose more complex functions.
A function that returns a value is called a fruitful function in this book. The opposite of a fruitful function is void function — one that is not executed for its resulting value, but is executed because it does something useful. (Languages like Java, C#, C and C++ use the term “void function”, other languages like Pascal call it a procedure.) Even though void functions are not executed for their resulting value, Javascript always wants to return something. So if the programmer doesn’t arrange to return a value, Javascript will automatically return the value undefined
.
How do we write our own fruitful function? Let’s look at the standard formula for compound interest as an example of a fruitful function:
/*
Apply the compound interest formula to p
to produce the final amount.
*/
function finalAmt (p, r, n, t) {
let a = p * (1 + r/n) ** (n * t);
// This is new, and makes the function fruitful
return a;
}
//now that we have the function above, let's call it
let toInvest = Number.parseFloat( window.prompt("How much do you want to invest?") );
let fnl = finalAmt(toInvest, 0.08, 12, 5);
console.log("At the end of the period you'll have", fnl);
a
in this case). This expression will be evaluated and returned to the caller as the “fruit” of calling this function.toInvest
is a string, but we need a number before we can work with it. Javascript can automatically convert it to a number for us when we use it in the calculation. Because it is money, and could have decimal places, we’ve used the Number.parseFloat
type converter function to parse the string and return a float.100
when prompted for the amount to invest, we get the outputlet toInvest = Number.parseFloat( window.prompt("How much do you want to invest?") );
also shows yet another example of composition — we can call a function like Number.parseFloat
, and its arguments can be the results of other function calls (like window.prompt
) that we’ve called along the way.Notice something else very important here. The name of the variable we pass as an argument — toInvest
— has nothing to do with the name of the parameter — p
. It is as if p = toInvest
is executed when finalAmt
is called. It doesn’t matter what the value was named in the caller, in finalAmt
its name is p
.
These short variable names are getting quite tricky, so perhaps we’d prefer one of these versions instead:
function finalAmtV2(principalAmount, nominalPercentageRate, numTimesPerYear, years) {
let a = principalAmount * (1 + nominalPercentageRate / numTimesPerYear) ** (numTimesPerYear * years);
return a;
}
function finalAmtV3(amt, rate, compounded, years) {
let a = amt * (1 + rate / compounded) ** (compounded * years);
return a;
}
They all do the same thing. Use your judgment to write code that can be best understood by other humans!
Short variable names are more economical and sometimes make code easier to read: E = mc2 would not be nearly so memorable if Einstein had used longer variable names! If you do prefer short names, make sure you also have some comments to enlighten the reader about what the variables are used for.
When we declare a new local variable inside a function, it only exists inside the function, and we cannot use it outside. For example, consider again this function:
If we try to use a
, outside the function, we’ll get an error like this:
The variable a
is local to finalAmt
, and is not visible outside the function.
Additionally, a
only exists while the function is being executed — we call this its lifetime. When the execution of the function terminates, the local variables are destroyed.
Parameters are also local, and act like local variables. For example, the lifetimes of p
, r
, n
, t
begin when finalAmt
is called, and the lifetime ends when the function completes its execution.
So it is not possible for a function to set some local variable to a value, complete its execution, and then when it is called again next time, recover the local variable. Each call of the function creates new local variables, and their lifetimes expire when the function returns to the caller.
A value provided to a function when the function is called. This value is assigned to the corresponding parameter in the function. The argument can be the result of an expression which may involve operators, operands and calls to other fruitful functions.
The second part of a compound statement. The body consists of a sequence of statements all indented the same amount from the beginning of the header. The standard amount of indentation used within the Python community is 4 spaces.
A statement that consists of two parts:
The syntax of a compound statement looks like this:
The order in which statements are executed during a program run.
A box in a stack diagram that represents a function call. It contains the local variables and parameters of the function.
A named sequence of statements that performs some useful operation. Functions may or may not take parameters and may or may not produce a result.
A statement that executes a function. It consists of the name of the function followed by a list of arguments enclosed in parentheses.
Using the output from one function call as the input to another.
A statement that creates a new function, specifying its name, parameters, and the statements it executes.
A function that returns a value when it is called.
The first part of a compound statement. A header line begins with a keyword and ends with a colon (:)
A statement which permits functions and variables defined in another Python module to be brought into the environment of another script. To use the features of the turtle, we need to first import the turtle module.
Variables and objects have lifetimes — they are created at some point during program execution, and will be destroyed at some time.
A variable defined inside a function. A local variable can only be used inside its function. Parameters of a function are also a special kind of local variable.
A name used inside a function to refer to the value which was passed to it as an argument.
A fancy word to describe reorganizing our program code, usually to make it more understandable. Typically, we have a program that is already working, then we go back to “tidy it up”. It often involves choosing better variable names, or spotting repeated patterns and moving that code into a function.
A graphical representation of a stack of functions, their variables, and the values to which they refer.
A list of the functions that are executing, printed when a runtime error occurs. A traceback is also commonly referred to as a stack trace, since it lists the functions in the order in which they are stored in the runtime stack.
The opposite of a fruitful function: one that does not return a value. It is executed for the work it does, rather than for the value it returns.
Write a void (non-fruitful) function to that prints out a “hello” message. Your function should declare 3 parameters: firstName
, lastName
, and title
. title
will be Mr., Ms., Dr., etc. The function should print a message like this one: Hello Dr. Matthew Curinga.
Write a function half(num)
which returns the value of num
divided by 2.
Write a function triple(num)
which return num
* 3.
Write a function areaOfACircle(r)
which returns the area of a circle of radius r
. For the value of PI, use the constant Math.PI
(Hint: if you can’t remember how to find the area of a circle, look it up or ask a friend.)
Write a function hypotenuse(a, b)
which calculates the hypotenuse of a right triangle when given the length of sides a
and b
. Use the Pythagorean theorem a^2 + b^2 = c^2
.
Note, you will need to be able to calculate square roots to solve this problem. You can use the build in math function Math.sqrt
.
(hard bonus) Write a function called distance(x1, y1, x2, y2)
which calculates the distance between the point at (x1, y1) and (x2, y2) on a Cartesian Plane. You can find the formula for this at http://www.mathsisfun.com/algebra/distance-2-points.html
Use the hypotenuse
function from exercise 5 to compose this function.
This is the first “lab” in our textbook. For this project, you will be asked to write a complete program that solves a problem. The labs should break up parts of the program into different functions. Your program should start when the main()
function is called. main
is not a keyword in Javascript, but in many programming languages there is a convention that the starting function is called main
. Please take a look at the Tip Calculator case study below for a sense of how your program should be structured.
For this first lab, too, please pay attention to your coding style to make sure that the program is well formatted and easy for human readers to understand.
For this lab you are going to revisit the lemonade stand estimator from the exercises in chapter 2. You will make an interactive program that asks the user to enter all of the data for:
Once all of the data is entered, the program will print out a neatly formatted message with the results of the estimation.
For each of these problems, write the unit test first, then write the function. Make sure that it passes the test.
Convert km to m. For example, write a function that converts kilometers to meters, as well as a function that tests this code. There are 1,000 meters in 1 kilometer. The code might look like this:
function kmToM (km) {
let m = km / 1000;
return m;
}
function test_kmToM() {
assert(kmToM(1) === 1000, "1km should = 1000m");
assert(kmToM(52) === 52000, "52km should = 52000m");
assert(kmToM(.05) === 50, ".05km should = 50m");
assert(kmToM(-3) === -3000, "-3 should = -3000m");
assert(kmToM(0) === 0, "0 should = 0");
}
Tbs to cups. There are 16 tablespoons in a cup. Write a function that converts Tbs to cups. It should take the number of tablespoons as an argument and return the number of cups.
Kilos (kg) to pounds (lbs). Write a function that converts kilograms to pounds. Use the conversion rate of 2.2 pounds for each kilogram.
Weight of water. 1 liter of water weighs exactly 1 kilogram. Write a function that has a parameter for the volume of water in liters returns the weight of water in pounds, something like this: function litersOfWaterInLbs(liters)
Format currency. Write a function fmtCurrency(amount, symbol)
that takes a currency amount as a real number and returns a string, with the currency symbol at the start followed by the amount rounded to 2 decimal places (using toFixed
).
Making pizza. You make pizzas. When you make the sauce, if you only use 1 can of tomatoes, it’s not enough for the whole pie. You need 1.5 cans per pizza. When you are making a lot of pizzas (and a big pot of sauce), you want a function that tells you how many cans of tomatoes to buy. Of course, you can’t buy half a can of tomatoes, so you must round up if there’s a half can. To “round up”, use Javascript’s built in Math.round
function.
Javascript’s built-in Math.round(n)
rounds any real number to the closest integer. Write a more flexible function, round(n, p)
that rounds a number n
to the number of decimal places specified by p
.
Examples:
⠕ round(2.457, 2);
=> 2.46
⠕ round(3.98746144, 4)
=> 3.9875
⠕ round(3.98746144, 0)
=> 4
⠕ round(52, -1)
=> 50
⠕ round(55, -1)
=> 60
⠕ round(542, -2)
=> 540
Your tests should test all of these examples.
Now that we have fruitful functions, we can focus our attention on reorganizing our code so that it fits more nicely into our mental chunks.
This process of rearrangement is called refactoring the code.
With our Tip Calculator we need to find the amount of the tip and show the results to the user. In the example below, we separate the various functions of the program to make a more complete tip calculator. As you’ll see, we’re starting to build code that is useful. Using functions allows us to make changes to one part of a program without affecting other parts of the program. For example, we can change the welcome message without worrying about breaking our calculations.
The trick about refactoring code is to anticipate which things we are likely to want to change each time we call the function: these should become the parameters, or changeable parts, of the functions we write.
/**
* tip-calulator.js
* This example program helps the user
* calculate the amount of tip to leave
* on a restaurant bill.
* by Matt Curinga
* 15 Sep 2018
*
*/
/*
Present the user with a welcome message.
*/
function welcome() {
let msg = `
-----------------------------------
Welcome to the Tip Calculator
-----------------------------------
`;
console.log(msg);
}
/*
Calculate the tip on a bill, given the pct of the tip.
Return the amount of the tip
*/
function calcTip(bill, pct) {
// convert pct to a decimal and calculate
let tip = bill * pct;
// convert the tip to float with 2 decimal places
tip = Number.parseFloat(tip);
return tip;
}
/*
Ask the user to enter the amount of the bill
and return this amount as a <float>
*/
function askBillAmt() {
let amt = window.prompt("How much was your total bill?");
amt = Number.parseFloat(amt);
return amt;
}
/*
Allow the user to choose a tip amount from a menu.
*/
function askTipPct() {
let pct = window.prompt("What percent tip do you want to leave?");
// convert the pct from a whole number to a fraction
pct /= 100;
pct = Number.parseFloat(pct).toFixed(2);
return pct;
}
function money(amt) {
let dollars = "$" + Number.parseFloat(amt).toFixed(2);
return dollars;
}
/*
Prints a message to the user showing
the result of the calculations.
*/
function showResults(bill, tip, pct) {
let total = tip + bill;
console.log("Bill amount: " + money(bill) );
// multiply % by 100 to convert back
console.log("Tip percentage: " + (pct * 100) + "%" );
console.log("Tip amount due: " + money(tip) );
console.log("Total with tip: " + money(total) );
console.log(`
-----------------------------------
GOOD BYE
-----------------------------------
`);
}
/*
Read in the basic information, calcualte the tip
and the share, then dispaly the results to the user.
*/
function main() {
welcome();
let myBill = askBillAmt();
let pct = askTipPct();
let tip = calcTip(myBill, pct);
showResults(myBill, tip, pct);
}
main();
A successful attempt to express logical propositions by symbols, the laws of whose combinations should be founded upon the laws of the mental processes which they represent, would, so far, be a step towards a philosophical language.
— George Boole, The Mathematical Analysis of Logic 1847
A Boolean value is either true or false. It is named after the British mathematician, George Boole, who first formulated Boolean algebra — some rules for reasoning about and combining these values. This is the basis of all modern computer logic.
In Javascript, the two Boolean values are true
and false
(the capitalization must be exactly as shown), and the Javascript type is ‘boolean’.
A Boolean expression is an expression that evaluates to produce a result which is a Boolean value. For example, the operator ===
tests if two values are equal. It produces (or yields) a Boolean value:
⠕ 5 === (3 + 2); // Is five equal 5 to the result of 3 + 2?
=> true
⠕ 5 === 6;
=> false
⠕ let j = "hel";
⠕ j + "lo" === "hello";
true
In the first statement, the two operands evaluate to equal values, so the expression evaluates to true
; in the second statement, 5 is not equal to 6, so we get false
.
The ===
operator is one of six common comparison operators which all produce a boolean
result; here are all six:
x === y // Produce true if x is equal to y
x !== y // Produce true if x is not equal to y
x > y // Produce true if x is greater than y
x < y // Produce true if x is less than y
x >= y // Produce true if x is greater than or equal to y
x <= y // Produce true if x is less than or equal to y
Although these operations are probably familiar, the Javascript symbols are different from the mathematical symbols. A common error is to use a single equal sign (=
) instead of a triple equal sign (===
). Remember that =
is an assignment operator and ===
is a comparison operator. Also, there is no such thing as =<
or =>
.
Like any other types we’ve seen so far, Boolean values can be assigned to variables, printed, etc.
There are three logical operators, and &&
, or ||
, and not !
, that allow us to build more complex Boolean expressions from simpler Boolean expressions. The semantics (meaning) of these operators is similar to their meaning in English. For example, x > 0 && x < 10
produces true
only if x
is greater than 0 and at the same time, x is less than 10.
n % 2 == 0 || n % 3 == 0
is true
if either of the conditions are true
, that is, if the number n
is divisible by 2 or it is divisible by 3. (What do you think happens if n
is divisible by both 2 and by 3 at the same time? Will the expression yield true
or false
? Try it in your Javascript interpreter.)
Finally, the not
operator negates a Boolean value, so ! (x > y)
is true
if (x > y)
is false
, that is, if x
is less than or equal to y
.
The expression on the left of the ||
operator is evaluated first: if the result is true
, Javascript does not (and need not) evaluate the expression on the right — this is called short-circuit evaluation. Similarly, for the &&
operator, if the expression on the left yields false
, Javascript does not evaluate the expression on the right.
So there are no unnecessary evaluations.
A truth table is a small table that allows us to list all the possible inputs, and to give the results for the logical operators. Because the &&
and ||
operators each have two operands, there are only four rows in a truth table that describes the semantics of &&
.
a | b | a && b |
---|---|---|
False | False | False |
False | True | False |
True | False | False |
True | True | True |
In a Truth Table, we sometimes use T and F as shorthand for the two Boolean values: here is the truth table describing ||
:
a | b | a || b |
---|---|---|
F | F | F |
F | T | T |
T | F | T |
T | T | T |
The third logical operator, !
, only takes a single operand, so its truth table only has two rows:
a | !a |
---|---|
F | T |
T | F |
A set of rules for simplifying and rearranging expressions is called an algebra. For example, we are all familiar with school algebra rules, such as:
which provides rules for working with Boolean values.
First, the &&
operator:
x && false === false
false && x === false
y && x === x && y
x && true === x
true && x === x
x && x === x
Here are some corresponding rules for the ||
operator:
x || false === x
false || x === x
y || x === x || y
x || true === true
true || x === true
x || x === x
Two !
operators cancel each other:
In order to write useful programs, we almost always need the ability to check conditions and change the behavior of the program accordingly. Conditional statements give us this ability. The simplest form is the if statement:
if (x % 2 == 0) {
console.log(x, " is even.");
console.log("Did you know that 2 is the only even number that is prime?");
}
else {
console.log(x, " is odd.") ;
console.log("Did you know that multiplying two odd numbers always gives an odd result?");
}
If it is true, then all the indented statements get executed. If not, then all the statements indented under the else
clause get executed.
The syntax for an if
statement looks like this:
if BOOLEAN EXPRESSION {
STATEMENTS_1 // Executed if condition evaluates to true
}
else {
STATEMENTS_2 // Executed if condition evaluates to false
}
As with the function definition from the last chapter and other compound statements like for
, the if
statement consists of a header line and a body. The header line begins with the keyword if
followed by a Boolean expression and ends with a left curly brace ( { ).
The indented statements that follow are called a block. The block ends with the right curly brace ( } ).
Each of the statements inside the first block of statements are executed in order if the Boolean expression evaluates to true
. The entire first block of statements is skipped if the Boolean expression evaluates to false
, and instead all the statements indented under the else
clause are executed.
There is no limit on the number of statements that can appear under the two clauses of an if
statement, but there has to be at least one statement in each block.
else
clauseAnother form of the if
statement is one in which the else
clause is omitted entirely. In this case, when the condition evaluates to true
, the statements are executed, otherwise the flow of execution continues to the statement after the if
.
if (x < 0) {
console.log("The negative number ", x, " is not valid here.");
x = 42;
console.log("I've decided to use the number 42 instead.");
}
console.log("The square root of ", x, "is", Math.sqrt(x)) ;
In this case, the print function that outputs the square root is the one after the if
— it comes after our curly braces ended the conditional block.
Notice that else
is not a statement. The if
statement has two clauses, one of which is the (optional) else
clause. However you can never use the else
keyword outside of an if
statement.
Sometimes there are more than two possibilities and we need more than two branches. One way to express a computation like that is a chained conditional:
Again, exactly one branch will be executed. There is no limit of the number of else if
statements but only a single (and optional) final else
statement is allowed and it must be the last branch in the statement:
if (choice === "a") {
functionOne();
}
else if (choice == "b") {
functionTwo();
}
else if (choice === "c") {
functionThree();
}
else {
console.log("Invalid choice.");
}
Each condition is checked in order. If the first is false, the next is checked, and so on. If one of them is true, the corresponding branch executes, and the statement ends. Even if more than one condition is true, only the first true branch executes.
One conditional can also be nested within another. (It is the same theme of composability, again!) We could have written the previous example as follows:
The outer conditional contains two branches. The second branch contains another if
statement, which has two branches of its own. Those two branches could contain conditional statements as well.
Although the indentation of the statements makes the structure apparent, nested conditionals very quickly become difficult to read. In general, it is a good idea to avoid them when we can.
Logical operators often provide a way to simplify nested conditional statements. For example, we can rewrite the following code using a single conditional:
if (0 < x) { // Assume x is a number here
if (x < 10) {
console.log("x is a positive single digit.");
}
}
The console.log
function is called only if we make it past both the conditionals, so instead of the above which uses two if
statements each with a simple condition, we could make a more complex condition using the &&
operator. Now we only need a single if
statement:
return
statementThe return
statement, with or without a value, depending on whether the function is fruitful or void, allows us to terminate the execution of a function before (or when) we reach the end. One reason to use an early return is if we detect an error condition:
function printSquareRoot(x) {
if (x <= 0) {
console.log("Positive numbers only, please.");
return;
}
console.log("The square root of", x, "is", result)
}
The function printSquareRoot
has a parameter named x
. The first thing it does is check whether x
is less than or equal to 0, in which case it displays an error message and then uses return
to exit the function. The flow of execution immediately returns to the caller, and the remaining lines of the function are not executed.
As you integrate Boolean logic into your programs, you will often encounter the pattern where you test if a single value evaluates to true
or false
. The Boolean condition can be written without a comparison operator because the value itself will be resolves to true
or false
.
Any value that is not false
, undefined
, null
, 0, NaN
, or an empty string (’’) actually returns true when tested as a conditional statement. Consider the following code:
let first = "Diego";
let last = "";
let email = "diego@example.com";
if (email) {
// this block will be executed because email
console.log("Email evaluted to true");
}
if (email === true) {
// this block won't be executed because email is not equal to true
console.log("Email doesn't equal true");
}
if (!last) {
// this block (using not) executes
// because last is an empty string, it evaluates to false
console.log("Enter your last name!");
}
In the above example we can say that email
is truthy because it evaluates to true
even though it doesn’t equal true
. Likewise, last
is falsy — it evaluates to false
because it’s an empty string, not equal to false
.
Each of the six relational operators has a logical opposite: for example, suppose we can get a driving license when our age is greater or equal to 16, we can not get the driving license when we are less than 16.
Notice that the opposite of >=
is <
.
operator | logical opposite |
---|---|
=== | !== |
!== | === |
< | >= |
<= | > |
> | <= |
>= | < |
Understanding these logical opposites allows us to sometimes get rid of !
operators. !
operators are often quite difficult to read in computer code, and our intentions will usually be clearer if we can eliminate them.
For example, if we wrote this Javascript:
it would probably be clearer to use the simplification laws, and to write instead:
Two powerful simplification laws (called de Morgan’s laws) that are often helpful when dealing with complicated Boolean expressions are:
!(x && y) === (!x) || (!y)
!(x || y) === (!x) && (!y)
For example, suppose we can slay the dragon only if our magic lightsabre sword is charged to 90% or higher, and we have 100 or more energy units in our protective shield. We find this fragment of Javascript code in the game:
if !((swordCharge >= 0.90) && (shieldEnergy >= 100)) {
console.log("Your attack has no effect, the dragon fries you to a crisp!");
}
else {
console.log("The dragon crumples in a heap. You rescue the gorgeous prince!");
}
de Morgan’s laws together with the logical opposites would let us rework the condition in a (perhaps) easier to understand way like this:
if (swordCharge < 0.90) || (shieldEnergy < 100) {
console.log("Your attack has no effect, the dragon fries you to a crisp!");
}
else {
console.log("The dragon crumples in a heap. You rescue the gorgeous prince!");
}
We could also get rid of the !
by swapping around the then
and else
parts of the conditional. So here is a third version, also equivalent:
if (swordCharge >= 0.90) and (shieldEnergy >= 100) {
console.log("The dragon crumples in a heap. You rescue the gorgeous prince!");
}
else {
console.log("Your attack has no effect, the dragon fries you to a crisp!");
}
This last version is probably the best of the three, because it very closely matches
the initial English statement. Clarity of our code (for other humans), and making it easy to see that the code does what we expect should always be a high priority.
As our programming skills develop we’ll find we have more than one way to solve any problem. So good programs are designed. We make choices that favor clarity, simplicity, and elegance. The job title software architect says a lot about what we do — we are architects who engineer our products to balance beauty, functionality, simplicity and clarity in our creations.
A group of consecutive statements with the same indentation.
The block of statements in a compound statement that follows the header.
Some rules for rearranging and reasoning about Boolean expressions.
An expression that is either true or false.
There are exactly two Boolean values: true
and false
. Boolean values result when a Boolean expression is evaluated by the Javascript interpreter. They have type 'boolean'
.
One of the possible paths of the flow of execution determined by conditional execution.
A conditional branch with more than two possible flows of execution. In Javascript chained conditionals are written with if ... else if ... else
statements.
Javascript operators that compare two values: ===
, !==
, >
, <
, >=
, and <=
.
The Boolean expression in a conditional statement that determines which branch is executed.
A statement that controls the flow of execution depending on some condition. In Javascript the keywords if
, else if
, and else
are used for conditional statements.
One of the operators that combines Boolean expressions: (and) &&
, (or) ||
, and (not) !
.
One program structure within another, such as a conditional statement inside a branch of another conditional statement.
A visual cue that tells the user that the system is ready to accept input data.
A concise table of Boolean values that can describe the semantics of an operator.
An explicit function call that takes a value of one type and computes a corresponding value of another type.
The process of adding a function header and parameters to a sequence of program statements is often referred to as “wrapping the code in a function”. This process is very useful whenever the program statements in question are going to be used multiple times. It is even more useful when it allows the programmer to express their mental chunking, and how they’ve broken a complex problem into pieces.
You can use this repl for your exercises
Assume the days of the week are numbered 0,1,2,3,4,5,6 from Sunday to Saturday. Write a function which is given the day number, and it returns the day name (a string).
You go on a wonderful vacation leaving on day number 3 (a Wednesday). You return home after 22 nights sleep. What day of the week is it? Write a general version of the program which asks for the starting day number, and the length of your stay, and it will tell you the name of day of the week you will return on. You might want to use the %
mod operator. You can compose this function from the one you wrote in exercise 1.
a > b
a >= b
a >= 18 and day == 3
a >= 18 and day != 3
3 === 3
3 !== 3
3 >= 4
!(3 < 4)
Write a function which is given an exam score, and it returns a string — the letter grade for that mark — according to this scheme:
Score | Grade |
---|---|
90-100 | A |
80-89 | B |
70-79 | C |
65-69 | D |
<65 | F |
(hard bonus) Write a function isRightAngled
which, given the length of three sides of a triangle, will determine whether the triangle is right-angled. Assume that the third argument to the function is always the longest side. It will return true
if the triangle is right-angled, or false
otherwise.
Hint: Floating point arithmetic is not always exactly accurate, so it is not safe to test floating point numbers for equality. If a good programmer wants to know whether x
is equal or close enough to y
, they would probably code it up as:
If you’re intrigued by why floating point arithmetic is sometimes inaccurate, on a piece of paper, divide 10 by 3 and write down the decimal result. You’ll find it does not terminate, so you’ll need an infinitely long sheet of paper. The representation of numbers in computer memory or on your calculator has similar problems: memory is finite, and some digits may have to be discarded, so small inaccuracies creep in. Try this script:
This section describes three lab assignments that ask you to write larger programs that are organized into several functions. To complete these labs you will combine what you have learned about variables and expressions, functions, and conditional Boolean expressions.
Body Mass Index (BMI) is a ratio of weight to height (squared) that public health and other organizations use as a guide for healthy weights. It is expressed as In this lab you will write a BMI calculator that asks the user for their age, height, and weight. You will calculate their BMI and then report a message indicating if they are at risk for being underweight, healthy weight, overweight, or obese.
BMI can be calculated using metric or SI measurements for height and weitht. The standard index comes from metric units: If you choose to use SI rather than metric measurements, you can use inches as the units for height and pounds as the unit for weight, and then convert the result by multiplying by the constant 703
. The formula would be:
BMI for children and teens is more complicated, so you will not calculate it for this lab. Instead, if the age is < 20, you should show the user a message that indicates that your calculator is only for adults.
Once you have calculated the user’s BMI, you should use the following chart to determine which weight category they are in. Use this table:
BMI | Category |
---|---|
< 18.5 | underweight |
18.5-24.9 | normal weight |
25-29.9 | overweight |
> 29.9 | obese |
Chinese animal signs are determined based on the year of birth and repeat on a 12 year cycle. For this lab, we are going to write a program that asks the user the year they were born and then tells them their animal sign. We will use a simplified version of the calendar which matches animal-year to the Western calendar year. Use the table below for the starting years (and then each animal repeats 12 years later). You will want to use the mod %
operator for this problem.
Animal | Birth Year |
---|---|
Rat | 1924 |
Ox | 1925 |
Tiger | 1926 |
Rabbit | 1927 |
Dragon | 1928 |
Snake | 1929 |
Horse | 1930 |
Goat | 1931 |
Monkey | 1932 |
Rooster | 1933 |
Dog | 1934 |
Pig | 1935 |
This may be an interesting problem if you are comfortable with geometry and trigonometry. In this lab you’ll revisit the randomWalk
program that we worked on in our turtle graphics. In the randomWalk
the turtle moves in a loop where it chooses a random angle to turn and a random distance for each iteration of the loop. You will modify randomWalk
so that the turtle “bounces” when it reaches the edge of the screen. By bounce, the turtle should advance to the edge of the screen (aka canvas) and then turn to the angle of reflection away from the edge. To finish its “walk”, it should then move forward for whatever distance remains for this iteration. Some hints:
https://repl.it/@mcuringa/Random-Walk
x
and y
that allow the turtle to stay on the screen. The turtle canvas dimensions are 1920 x 1280
x
, y
and determine if it’s on the screen or off the screengetHeading()
to make this calculationfor
loopsComputers are often used to automate repetitive tasks. Repeating identical or similar tasks without making errors is something that computers do well and people do poorly.
Repeated execution of a set of statements is called iteration. Because iteration is so common, Javascript provides several language features to make it easier. The for
loop is the form of iteration you’ll likely be using most often, and we will look at that first. In the next chapter we’ve going to look at the while
statement — another way to have your program repeat code. After you understand both for
and while
loops, you will be able to use them both to solve problems.
for
loopA basic building block of all programs is to be able to repeat some code, over and over again. Javascript’s for loop solves this for us. The syntax for the for
loop is:
We can see that for
follows the familiar pattern for block statements that we have already seen for functions and if/else statements, with a header and then body between opening and closing curly braces ({}); In the case of for
, the header contains 3 statements inside the parenthesis.
true
, the loop body executes. The loop body may contain any number of Javascript statements.We have already played around with the for
loop with our turtle graphics when we were looking at the variables in a spiral. Let’s take a closer look at this function which contains a for loop.
function spiral () {
setColor("deeppink")
let distance = 2;
let angle = 91;
for (let i=0; i<500; i++) {
fd(distance);
distance += 2;
rt(angle);
}
}
i
in the for
statement at line 5 is the loop variable. We could have chosen any other variable name instead, but i
is a common convention, being short for the loop index.let i = 0;
spiral
’s loop body contains 3 statements which (line 5) move the turtle forward, (line 6) increment the distance
variable, and (line 7) rotate the turtle.true
the loop runs for another iteration. If false
, the loop terminates.for
header to run the final expression. The final expression here increments i
by one, using the special ++
operator.spiral
, since there are no other statements in the function body, the function returns undefined
since it is a void function.As we have mentioned previously, it is legal to make more than one assignment to the same variable. A new assignment makes an existing variable refer to a new value (and stop referring to the old value).
let airTimeRemaining = 15;
console.log(airTimeRemaining);
airTimeRemaining = 7;
console.log(airTimeRemaining);
The output of this program is:
because the first time airTimeRemaining
is printed, its value is 15, and the second time, its value is 7.
It is especially important to distinguish between an assignment statement and a Boolean expression that tests for equality. Because Javascript uses the equal token (=
) for assignment, it is tempting to interpret a statement like a = b
as a Boolean test. Unlike mathematics, it is not! Remember that the Javascript token for the equality operator is ===
.
Note too that an equality test is symmetric, but assignment is not. For example, if a === 7
then 7 === a
. But in Javascript, the statement a = 7
is legal and 7 = a
is not.
In Javascript, an assignment statement can make two variables equal, but because further assignments can change either of them, they don’t have to stay that way:
let a = 5;
let b = a; // After executing this line, a and b are now equal
a = 3; // After executing this line, a and b are no longer equal
The third line changes the value of a
but does not change the value of b
, so they are no longer equal. (In some programming languages, a different symbol is used for assignment, such as <-
or :=
, to avoid confusion. Some people also think that variable was an unfortunate word to choose, and instead we should have called them assignables. Javascript chooses to follow common terminology and token usage, also found in languages like C, C++, Java, and C#, so we use the tokens =
for assignment, ==
(or ===
which we prefer in Javascript) for equality, and we talk of variables.
When an assignment statement is executed, the right-hand side expression (i.e. the expression that comes after the assignment token) is evaluated first. This produces a value. Then the assignment is made, so that the variable on the left-hand side now refers to the new value.
One of the most common forms of assignment is an update, where the new value of the variable depends on its old value. Deduct 40 cents from my airtime balance, or add one run to the scoreboard.
Line 2 means get the current value of n, multiply it by three and add one, and assign the answer to n, thus making n refer to the value
.
So after executing the two lines above, n
will point/refer to the integer 16.
If you try to get the value of a variable that has never been assigned to, you’ll get an error:
Before you can update a variable, you have to initialize it to some starting value, usually with a simple assignment:
Line 3 — updating a variable by adding 1 to it — is very common.
It is called an increment of the variable; subtracting 1 is called a decrement.
Sometimes programmers also talk about bumping a variable, which means the same as incrementing it by 1.
Incrementing a variable is so common that Javascript provides an abbreviated syntax for it:
count++
is an abreviation for count = count + 1
. We pronounce the operator as “plus-plus”. Javascript offers a different operator (+=
pronounced “plus-equals”) for increment values other than 1:
There are similar abbreviations for --
, -=
, *=
, /=
, and %=
:
One of the things loops are good for is generating tables. Before computers were readily available, people had to calculate logarithms, sines and cosines, and other mathematical functions by hand. To make that easier, mathematics books contained long tables listing the values of these functions. Creating the tables was slow and boring, and they tended to be full of errors.
When computers appeared on the scene, one of the initial reactions was, “This is great! We can use the computers to generate the tables, so there will be no errors.” That turned out to be true (mostly) but shortsighted. Soon thereafter, computers and calculators were so pervasive that the tables became obsolete.
Well, almost. For some operations, computers use tables of values to get an approximate answer and then perform computations to improve the approximation. In some cases, there have been errors in the underlying tables, most famously in the table the Intel Pentium processor chip used to perform floating-point division.
Although a log table is not as useful as it once was, it still makes a good example of iteration. The following program outputs a sequence of values in the left column and 2 raised to the power of that value in the right column:
The string "\t"
represents a tab character. The backslash character in "\t"
indicates the beginning of an escape sequence. Escape sequences are used to represent invisible characters like tabs and newlines. The sequence \n
represents a newline, breaking a string onto the next line.
An escape sequence can appear anywhere in a string; in this example, the tab escape sequence is the only thing in the string. How do you think you represent a backslash in a string?
As characters and strings are displayed on the screen, an invisible marker called the cursor keeps track of where the next character will go. After calling console.log
, the cursor normally goes to the beginning of the next line.
The tab character shifts the cursor to the right until it reaches one of the tab stops. Tabs are useful for making columns of text line up, as in the output of the previous program:
Because of the tab characters between the columns, the position of the second column does not depend on the number of digits in the first column.
A two-dimensional table is a table where you read the value at the intersection of a row and a column. A multiplication table is a good example. Let’s say you want to print a multiplication table for the values from 1 to 6.
A good way to start is to write a loop that prints the multiples of 2, all on one line. Because console.log
moves the cursor to the next line, we’re going to accumulate our data into a string variable, and then print out the whole row with a call to console.log
.
Here we initialized our loop variable i
to 1 rather than 0 because we want to start counting from 1. As the loop executes, the value of i
changes from 1 to 6. When i
is incremented to 7, the loop terminates. Each time through the loop, it displays the value of 2 * i
, followed by the tab escape character.
We use the +=
operator to concatenate each value of i * 2
to the string row
. After our for
statement terminates, we print the results with console.log
.
The output of the program is:
So far, so good. The next step is to encapsulate and generalize.
Encapsulation is the process of wrapping a piece of code in a function, allowing you to take advantage of all the things functions are good for. You have already seen some examples of encapsulation, including including the square
function for our turtle graphics.
Generalization means taking something specific, such as printing the multiples of 2, and making it more general or abstract, such as printing the multiples of any integer. We can use function parameters to accomplish generalization and abstraction.
This function encapsulates the previous loop and generalizes it to print multiples of n
:
function printMultiples (n) {
let row = "";
for (let i=1; i<7; i++) {
row += (n * i) + "\t";
}
console.log(row);
}
To encapsulate, all we had to do was add the first line, which declares the name of the function and the parameter list. To generalize, all we had to do was replace the value 2 with the parameter n
.
If we call this function with the argument 2, we get the same output as before. With the argument 3, the output is:
With the argument 4, the output is:
By now you can probably guess how to print a multiplication table — by calling printMultiples
repeatedly with different arguments. In fact, we can use another loop:
Notice how similar this loop is to the one inside printMultiples
. All we did was replace the print
function with a function call.
The output of this program is a multiplication table:
To demonstrate encapsulation again, let’s take the code from the last section and wrap it up in a function:
With our printMultTable
we encapsulate the code for printing the multiplication table.
This process is a common development plan. We develop code by writing lines of code outside any function, or typing them in to the interpreter. When we get the code working, we extract it and wrap it up in a function.
This development plan is particularly useful if you don’t know how to divide the program into functions when you start writing. This approach lets you design as you go along.
You might be wondering how we can use the same variable, i
, in both printMultiples
and printMultTable
. Doesn’t it cause problems when one of the functions changes the value of the variable?
The answer is no, because the i
in printMultiples
and the i
in printMultTable
are not the same variable.
Variables declared with let
inside a function definition are local; you can’t access a local variable from outside its home function. That means you are free to have multiple variables with the same name as long as they are not in the same function.
It is common and perfectly legal to have different local variables with the same name. In particular, names like i
and j
are used frequently as loop variables. If you avoid using them in one function just because you used them somewhere else, you will probably make the program harder to read.
As another example of generalization, imagine you wanted a program that would print a multiplication table of any size, not just the six-by-six table. You could add a parameter to printMultTable
:
We replaced the loop condition i<7
to use the parameter high
. Because we want to print upt to and including high
, we changed the Boolean operator from <
to <=
. If we call printMultTable
with the argument 7, it displays:
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36
7 14 21 28 35 42
This is fine, except that we probably want the table to be square — with the same number of rows and columns. We have high
number of rows, but our old version of printMultiples
stops at the literal value <7
. To do improve our code, we add another parameter to printMultiples
to specify how many columns the table should have.
Just to be annoying, we call this parameter high
, demonstrating that different functions can have parameters with the same name (just like local variables). Here’s the whole program:
function printMultiples (n, high) {
let row = "";
for (let i=1; i <= high; i++) {
row += (n * i) + "\t";
}
console.log(row);
}
function printMultTable (high) {
for (let i=1; i <= high; i++) {
printMultiples(i, high);
}
}
Notice that when we added a new parameter, we had to change the first line of the function (the function heading), and we also had to change the place where the function is called in printMultTable
.
Now, when we call printMultTable(7)
:
for
loopsThe for
loop is a definite loop – a type of iteration where the number of iterations is (typically) known when the loop begins. In the examples we’ve looked at, we’ve used it to iterate over a range of numbers – to execute the loop a set number of times. In later chapters we’ll learn how to use the for
loop to iterate over lists of items as well. We can use a for
loop to search through one of these lists, whether a range of numbers or a list of items. For now, we’ll stick with ranges of numbers.
Let’s say we want to write a function that counts the multiples of 3 in a range of numbers. We already learned how to use the if
statement to test a condition. In this chapter we looked at ways to iterate over a range of numbers. We can combine these techniques to solve our problem.
function countMultiplesOf3 (start, end) {
let count = 0;
for (let i=start; i <= end; i++) {
if (i % 3 === 0) {
count++;
}
}
return count;
}
console.log(countMultiplesOf3(3, 12)); // the multiples are [3, 6, 9, 12], so prints 4
In the example above, we test to see if a number is evenly divisible by 3 (i.e. a multiple of 3) using the %
mode operator. We use the count
variable to keep track of all of the multiples we find, using the ++
operator to increment count
each time we find a multiple in our loop.
Sometimes we want to exit a for
loop early. One way to do this is with a return
statement. Let’s write a simple function to test if a number is prime. A prime number is a positive integer greater than 1 that is only divisible by itself and by 1. To test if a number is prime, we will test all of the numbers smaller than it. If any of those numbers divide evenly into the number, it’s not prime. If our loop completes without finding a divisor, our number must be prime. Here’s the code:
function isPrime (n) {
if (n < 2) {
return false;
}
if (n === 2) {
return true;
}
for (let i = 2; i<n; i++) {
if (n % i === 0) {
return false;
}
}
return true;
}
On line 3, we make sure that n
is a positive integer greater than 1. On line 6, we handle the special case of 2 – the only even prime. On line 11 we begin our for
loop. We do at most enough iterations to check all of the numbers between 2 and n - 1
. If we find a divisor of n
on line 12, then we return false
because n
can’t be prime. We don’t need to continue our for
loop, so we exit early with a return
statement. If we finally reach line 17, we return true
. Because of the logic of our program, n
must be prime.
A few times now, we have mentioned all the things functions are good for. By now, you might be wondering what exactly those things are. Here are some of them:
if
and else
statements. Repetition of statements is accomplished by iteration, such as with a for
loop.
for
loopfor
loop is a finite loop that repeats the loop body a known number of times. for
loops are useful for repeating things a set number of times (e.g. do this 100 times, print all of the odd numbers to 5,000), and iterating through items on a list (e.g. send an email to the whole class roster).
Fork this repl with empty function definitions to get started
Write a function that prints We like Javascript!
1000 times.
Write a function printOdds(start, end)
which prints all of the odd number starting with start
up to and including end
.
Write a function countByTens
that counts to 10,000 by 10s (printing the sequence 10, 20, 30, .. 10,000).
Write a function named poly
that uses a for
loop to make a turtle draw any regular polygon (regular means all sides the same lengths, all angles the same, to find the angle, divide 360 by the number of sides). The function must have size
and numSides
as parameters. So, poly(40, 4)
would draw a square where the sides are 40 pixels long.
Write a function countPrimes(a, b)
which counts all of the prime numbers between a
and b
, including a
and b
. Your function must return the count. It should not print anything to the console.
Center text. The .length
attributes of a string tells us how many characters are in the string, "cat".length === 3
. Using this property and a for
loop, write a function that centers text in the console. Your function must have two parameters – the text that needs to be centered, and the number of characters in the console. The function returns a new string, padded with enough blank spaces on the left side so that it will be centered. The function does not print to the console.
(hard bonus) Consider the isPrime
example above. Our solution would be considered a naive solution – a solution which solve the problem in a basic, but not most efficient or elegant manner. Thinking (or reading) about prime numbers, can you refactor isPrime
so that it can determine if a number is prime without having to complete all of the iterations between 2 and n
?
For the for
loop lab we’re going to return to our turtle graphics programming. We’ve already seen many interesting shapes and patterns that can be made with loops. For this lab, you are going to use turtle to draw a picture, with these simple guidelines:
main()
which starts your programfor
loops to place more than one “think” in your pictureIf you are unsure how to start, consider drawing a city. You can write functions for buildings, windows, using for loops to place the windows on a building and building inside your drawing.
while
statementHere is a fragment of code that demonstrates the use of the while
statement:
/**
* Return the sum of 1+2+3 ... n
*/
function sumTo(n) {
let total = 0;
let v = 1;
while (v <= n) {
total += v;
v++;
}
return total;
}
console.log("sumTo(4) => 10", sumTo(4) === 10);
console.log("sumTo(1000) =>500500 ", sumTo(1000) === 500500);
You can almost read the while
statement as if it were English. It means, while v
is less than or equal to n
, continue executing the body of the loop. Within the body, each time, increment v
. When v
passes n
, return your accumulated sum.
More formally, here is precise flow of execution for a while
statement:
false
or true
.false
, exit the while
statement and continue execution at the next statement (line 8 in this case).true
, execute each of the statements in the body (lines 6 and 7) and then go back to the while
statement at line 5.The body consists of all of the statements indented below the while
keyword.
Notice that if the loop condition is false
the first time we test it, the statements in the body of the loop are never executed.
The body of the loop should change the value of one or more variables so that eventually the condition becomes false
and the loop terminates. Otherwise the loop will repeat forever, which is called an infinite loop. An endless source of amusement for computer scientists is the observation that the directions on shampoo, “lather, rinse, repeat”, are an infinite loop.
In the case here, we can prove that the loop terminates because we know that the value of n
is finite, and we can see that the value of v
increments each time through the loop, so eventually it will have to exceed n
. In other cases, it is not so easy, even impossible in some cases, to tell if the loop will ever terminate.
What you will notice here is that the while
loop is more work for you — the programmer — than the equivalent for
loop. When using a while
loop you have to manage the loop variable yourself: give it an initial value, test for completion, and then make sure you change something in the body so that the loop terminates. By comparison, here is an equivalent function that uses for
instead:
// Return the sum of 1+2+3 ... n
function sumTo(n) {
let ss = 0;
for (let v = 1; v <= n; v++) {
ss = ss + v;
}
return ss;
}
So why have two kinds of loop if for
looks easier? This next example shows a case where we need the extra power that we get from the while
loop.
Let’s look at a simple sequence that has fascinated and foxed mathematicians for many years. They still cannot answer even quite simple questions about this.
The “computational rule” for creating the sequence is to start from some given n
, and to generate the next term of the sequence from n
, either by halving n
, (whenever n
is even), or else by multiplying it by three and adding 1. The sequence terminates when n
reaches 1.
This Javascript function captures that algorithm:
// Print the 3n+1 sequence from n,
// terminating when it reaches 1.
function seq3np1 (n) {
let sequence = "";
while (n !== 1) {
sequence += n + ", ";
if (n % 2 === 0) { // n is even
n = Math.floor(n/2);
}
else { // n is odd
n = n * 3 + 1;
}
}
console.log(sequence + 1 + ".");
}
First, note that we use the accumulator pattern that we introduced in the last chapter to concatenate our output to the (initially empty) string, sequence
. Not until we complete the while
loop do we print sequence
to the console.
The condition for continuing with this loop is n !== 1
, so the loop will continue running until it reaches its termination condition, (i.e. n === 1
).
Each time through the loop, the program joins the value of n
to sequence
and then checks whether it is even or odd. If it is even, the value of n
is divided by 2 (and then rounded down using Math.floor
). If it is odd, the value is replaced by n * 3 + 1
. Because of the logic for how the loop variable (n
) changes in each iteration, this algorithm cannot be easily implemented with a for
loop, because the for
loop finalization statement cannot handle the boolean logic of deciding how to modify n
. In this case, while
is more suitable.
Here are some examples:
⠕ seq3np1(3)
3, 10, 5, 16, 8, 4, 2, 1.
⠕ seq3np1(19)
19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1.
⠕ seq3np1(21)
21, 64, 32, 16, 8, 4, 2, 1.
⠕ seq3np1(16)
16, 8, 4, 2, 1.
Since n
sometimes increases and sometimes decreases, there is no obvious proof that n
will ever reach 1, or that the program terminates. For some particular values of n
, we can prove termination. For example, if the starting value is a power of two, then the value of n
will be even each time through the loop until it reaches 1. The previous example ends with such a sequence, starting with 16.
See if you can find a small starting number that needs more than a hundred steps before it terminates.
Particular values aside, the interesting question was first posed by a German mathematician called Lothar Collatz: the Collatz conjecture (also known as the 3n + 1 conjecture), is that this sequence terminates for all positive values of n
. So far, no one has been able to prove it or disprove it!
(A conjecture is a statement that might be true, but nobody knows for sure.)
Think carefully about what would be needed for a proof or disproof of the conjecture “All positive integers will eventually converge to 1 using the Collatz rules”. With fast computers we have been able to test every integer up to very large values, and so far, they have all eventually ended up at 1. But who knows? Perhaps there is some as-yet untested number which does not reduce to 1.
You’ll notice that if you don’t stop when you reach 1, the sequence gets into its own cyclic loop: 1, 4, 2, 1, 4, 2, 1, 4 … So one possibility is that there might be other cycles that we just haven’t found yet.
Wikipedia has an informative article about the Collatz conjecture. The sequence also goes under other names (Hailstone sequence, Wonderous numbers, etc.), and you’ll find out just how many integers have already been tested by computer, and found to converge!
To write effective computer programs, and to build a good conceptual model of program execution, a programmer needs to develop the ability to trace the execution of a computer program. Tracing involves becoming the computer and following the flow of execution through a sample program run, recording the state of all variables and any output the program generates after each instruction is executed.
To understand this process, let’s trace the call to seq3np1(3)
from the previous section. At the start of the trace, we have a variable, n
(the parameter), with an initial value of 3. Since 3 is not equal to 1, the while
loop body is executed. 3 is printed and 3 % 2 === 0
is evaluated. Since it evaluates to false
, the else
branch is executed and 3 * 3 + 1
is evaluated and assigned to n
.
To keep track of all this as you hand trace a program, make a column heading on a piece of paper for each variable created as the program runs and another one for output. Our trace so far would look something like this:
n output printed so far
-- ---------------------
3 3,
10
Since 10 !== 1
evaluates to true
, the loop body is again executed, and 10 is printed. 10 % 2 === 0
is true, so the if
branch is executed and n
becomes 5. By the end of the trace we have:
n output printed so far
-- ---------------------
3 3,
10 3, 10,
5 3, 10, 5,
16 3, 10, 5, 16,
8 3, 10, 5, 16, 8,
4 3, 10, 5, 16, 8, 4,
2 3, 10, 5, 16, 8, 4, 2,
1 3, 10, 5, 16, 8, 4, 2, 1.
Tracing can be a bit tedious and error prone (that’s why we get computers to do this stuff in the first place!), but it is an essential skill for a programmer to have. From this trace we can learn a lot about the way our code works. We can observe that as soon as n
becomes a power of 2, for example, the program will require executions of the loop body to complete. We can also see that we don’t reach the final 1 within the body of the loop, which is why we concatenate 1
after our while
loop terminates.
In this function, we just store all of the values of the Collatz sequence in a single string. As we learn a bit more Javascript, we’ll be able to show you how to generate a list of values to hold the sequence, rather concatenating them to a string.
The following function counts the number of decimal digits in a positive integer:
function numDigits(n) {
let count = 0;
while (n != 0) {
count++;
n = Math.floor(n / 10);
}
return count;
}
A call to console.log(numDigits(710))
will print 3
. Trace the execution of this function call (using console.log
or just a piece of paper) to convince yourself that it works.
This function demonstrates an important pattern of computation called a counter. The variable count
is initialized to 0 and then incremented each time the loop body is executed. When the loop exits, count
contains the result — the total number of times the loop body was executed, which is the same as the number of digits.
If we wanted to only count digits that are either 0 or 5, adding a conditional before incrementing the counter will do the trick:
function numZeroAndNumFiveDigits(n) {
let count = 0;
while( n > 0) {
let digit = n % 10;
if (digit === 0 || digit === 5) {
count = count + 1
}
n = Math.floor(n / 10);
}
return count;
}
Confirm that numZeroAndNumFiveDigits(1055030250) === 7
.
Notice, however, that numDigits(0) === 0
. Explain why. Do you think this is a bug in the code, or a bug in the specifications, or our expectations, or the tests?
Loops are often used in programs that compute numerical results by starting with an approximate answer and iteratively improving it.
For example, before we had calculators or computers, people needed to calculate square roots manually. Newton used a particularly good method (there is some evidence that this method was known many years before). Suppose that you want to know the square root of n
. If you start with almost any approximation, you can compute a better approximation (closer to the actual answer) with the following formula:
Repeat this calculation a few times using your calculator. Can you see why each iteration brings your estimate a little closer? One of the amazing properties of this particular algorithm is how quickly it converges to an accurate answer — a great advantage for doing it manually.
By using a loop and repeating this formula until the better approximation gets close enough to the previous one, we can write a function for computing the square root. (In fact, this is how your calculator finds square roots — it may have a slightly different formula and method, but it is also based on repeatedly improving its guesses.)
This is an example of an indefinite
iteration problem: we cannot predict in advance how many times we’ll want to improve our guess — we just want to keep getting closer and closer. Our stopping condition for the loop will be when our old guess and our improved guess are “close enough” to each other.
Ideally, we’d like the old and new guess to be exactly equal to each other when we stop. But exact equality is a tricky notion in computer arithmetic when real numbers are involved. Because real numbers are not represented absolutely accurately (after all, a number like pi or the square root of two has an infinite number of decimal places because it is irrational), we need to formulate the stopping test for the loop by asking “is a
close enough to b
”? This stopping condition can be coded like this:
Notice that we take the absolute value of the difference between a
and b
.
function sqrt(n) {
let approx = n/2; // Start with some or other guess at the answer
let better = (approx + n/approx)/2;
while (Math.abs(approx - better) > .001) {
approx = better;
better = (approx + n/approx)/2.0;
}
return better;
}
// Test cases
console.log(sqrt(25));
console.log(sqrt(49));
console.log(sqrt(81));
The output is:
5.000000000016778
7
9.000000000004924
See if you can improve the approximations by changing the stopping condition. Also, step through the algorithm (perhaps by hand, using your calculator or by adding a count
variable to the loop) to see how many iterations were needed before it achieved this level of accuracy for sqrt(25)
.
Newton’s method is an example of an algorithm: it is a mechanical process for solving a category of problems (in this case, computing square roots).
Some kinds of knowledge are not algorithmic. For example, learning dates from history or your multiplication tables involves memorization of specific solutions.
But the techniques you learned for addition with carrying, subtraction with borrowing, and long division are all algorithms. Or if you are an avid Sudoku puzzle solver, you might have some specific set of steps that you always follow.
One of the characteristics of algorithms is that they do not require any intelligence to carry out. They are mechanical processes in which each step follows from the last according to a simple set of rules. And they’re designed to solve a general class or category of problems, not just a single problem.
Understanding that hard problems can be solved by step-by-step algorithmic processes (and having technology to execute these algorithms for us) is one of the major breakthroughs that has had enormous benefits. So while the execution of the algorithm may be boring and may require no intelligence, algorithmic or computational thinking — i.e. using algorithms and automation as the basis for approaching problems — is rapidly transforming our society. Some claim that this shift towards algorithmic thinking and processes is going to have even more impact on our society than the invention of the printing press. And the process of designing algorithms is interesting, intellectually challenging, and a central part of what we call programming.
Some of the things that people do naturally, without difficulty or conscious thought, are the hardest to express algorithmically. Understanding natural language is a good example. We all do it, but so far no one has been able to explain how we do it, at least not in the form of a step-by-step mechanical algorithm.
Some of these exercises work best with while
loops, while others can be solved more easily with for
loops. Some of them will work better if you write more than one function for the solution.
The most straightforward applications of loops, given what we have learned so far, is working with numbers. Please review some basic terms and concepts that will help you with these exercises.
A prime number is a whole number greater than 1 that can only be evenly divided by 1 and itself. Factors are number that multiply together produce a number. 2 and 3 are factors of 6 because . Is the result of multiple multiplying a number. 18 is a multiple of 3 because .
The site Math is Fun offers a straightforward definition of multiples and factors with links to other pages relevant to these problems.
You can get started by forking this repl with the function definitions
Sum range
Write a function sumRange(start, end)
that sums (adds up) all of the numbers between start
and end
, including start
and end
. Return the sum (a number).
Square Root Count
Revise the code above for Newton’s method for calculating square roots. Instead of returning the square root, it should return the number of iterations required to find the square root.
Sum of odds
sumOfOdds(start, end)
calculates and returns the sum of all of the odd numbers in the range start
..end
. It includes start
and end
if they are odd numbers.
Sum of Squares
Write a function that calculates and returns the sum of the squares of numbers in a range: sumOfSquare(start, end)
. The range includes start
and end
.
Greatest factor
Write a function greatestFactor(start, end, n)
that returns the greatest (highest) factor of n
in the range of numbers between start
and end
(including end).
Greatest Common Factor
Write a function gcm(a, b)
that returns the greatest (highest number) that is a factor of both a
and b
.
Factorials
Write a function fact(n)
which returns the factorial of n
(n!). The factorial of a number is that number multiplied by all of the numbers between itself and 1, so the factorial of 4 is . In Javascript, we’d say fact(4) === 24
.
Each term in the Fibonacci Sequence is calculated by adding the two previous numbers in the sequence together. The first two terms can by 0, and 1, so the next term is 1. The sequence begins 0, 1, 1, 2, 3, 5, 8, 13. Write a function fib(n)
which returns the n
th number in the sequence. So, fib(0) === 0
, fib(3) === 5
and so on.
An Armstrong Number is a number where the sum of the digits raised to the number of digits in the number, equal the number itself. 153 is an Armstrong Number. It has 3 digits, so Write a function isArmstrong(n)
that returns true
if n
is an Armstrong Number, or false
if it isn’t.
So far we have seen built-in types like number
, string
, and boolean
. We’ve also started working with arrays, although we’ll take a closer look in the next chapter.
Strings and arrays are qualitatively different from the others because they are made up of smaller pieces. In the case of strings, they’re made up of smaller strings each containing one character.
Types that comprise smaller pieces are called compound data types. Depending on what we are doing, we may want to treat a compound data type as a single thing, or we may want to access its parts. This ambiguity is useful.
Strings are objects. Objects can provide functions that work with the data of a string instance.
For example:
toUpperCase
is a method that can be invoked on any string object to create a new string, in which all the characters are in uppercase. (The original string ss
remains unchanged.)
There are also methods such as toLowerCase
, trim
, and repeat
that do other interesting things. To learn what methods are available and what they do, you can consult the MDN Documentation. Or, simply type the following into a Repl.it script:
When you type the period to select one of the methods of ss
, Repl.it will pop up a selection window showing all the methods that could be used on your string. There are lots of methods. In this chapter we’ll look at some of the most immediately useful ones.
The indexing operator (Javascript uses square brackets to enclose the index) selects a single character substring from a string:
The expression fruit[1]
selects character number 1 from fruit
, and creates a new string containing just this one character. The variable m
refers to the result. When we display m
, we could get a surprise:
Computer scientists always start counting from zero! The letter at subscript position zero of "banana"
is b
. So at position [1]
we have the letter a
.
If we want to access the zero-eth letter of a string, we just place 0, or any expression that evaluates to 0, inbetween the brackets:
The expression in brackets is called an index. An index specifies a member of an ordered collection, in this case the collection of characters in the string. The index indicates which one you want, hence the name. It can be any integer expression.
We already mentioned that the common variable name i
for loop variables can be thought of as shorthand for index. Consider this for
loop which iterates through the characters in a string:
produces:
If you take a close look at the loop condition from our example, you’ll see that our for
loop ends when i < fruit.length
. length
is a property of a string that lets us know how many characters are in the string. In later chapters we’ll see that arrays and other compound datatypes have a length
property that makes it easy for us to iterate over the items they contain.
Note that indexing string returns a string — Javascript has no special type for a single character. It is just a string of length 1.
We’ve also seen arrays previously. The same indexing notation works to extract elements from an array:
We’ve seen that the length
property of a string, returns the number of characters in the string:
To get the last letter of a string, you might be tempted to try something like this:
We see that last
is undefined
. The reason is that there is no character at index position 6 in "banana"
. Because we start counting at zero, the six indexes are numbered 0 to 5. To get the last character, we have to subtract 1 from the length of fruit
:
Here we see that the last character is fruit[fruit.length - 1]
, so the second to last character would be fruit[fruit.length - 2]
and so on.
for
loopA lot of computations involve processing a string one character at a time. Often they start at the beginning, select each character in turn, do something to it, and continue until the end. This pattern of processing is called a traversal. One way to encode a traversal is with a while
statement:
The loop condition is ix < fruit.length
, so when ix
is equal to the length of the string, the condition is false, and the body of the loop is not executed. The last character accessed is the one with the index fruit.length - 1
, which is the last character in the string.
But we’ve previously seen how the for
loop can easily iterate over the elements in a list and it can do so for strings as well:
As we iterate through the characters in fruit
, the loop variable, i
represents each index in the string. The loop continues until no characters are left. Here we can see the expressive power the for
loop gives us compared to the while loop when traversing a string.
The following example shows how to use concatenation and a for
loop to generate an abecedarian series. Abecedarian refers to a series or list in which the elements appear in alphabetical order. For example, in Robert McCloskey’s book Make Way for Ducklings, the names of the ducklings are Jack, Kack, Lack, Mack, Nack, Ouack, Pack, and Quack. This loop outputs these names in order:
let prefixes = "JKLMNOPQ";
let suffix = "ack";
for (let i = 0; i < prefixes.length; i++) {
console.log(prefixes[i] + suffix);
}
Of course, that’s not quite right because Ouack and Quack are misspelled. You’ll fix this as an exercise below.
A substring of a string is obtained by taking a smaller sequence of characters from an existing string to create a new string.
⠕ let s = "Pirates of the Caribbean";
⠕ console.log(s.substring(0,7));
Pirates
⠕ console.log(s.substring(11:14));
the
⠕ console.log(s.substring(15:24));
Caribbean
The substring(startIndex, endIndex)
method of a string, returns a new string without modifying the original string. The new string (i.e. the substring) starts at (and includes) the character at startIndex
and ends at (but excludes) the character at endIndex
. This behavior makes sense if you imagine the indices pointing between the characters, as in the following diagram:
If you imagine this as a piece of paper, substring(m, n)
copies out the part of the paper between the n
and m
positions. Provided m
and n
are both within the bounds of the string, your result will be of length (m-n).
There are a few more tricks to substring
. If you omit the second argument (endIndex
), the function returns a substring from startIndex
up to and including the last character in the string. If you provide an endIndex
greater than string.length
,
From the MDN documentation, we learn the following additional rules and behaviors for substring
.
indexEnd
argument is omitted, substring()
returns the substring from startIndex
to the end of the stringindexStart
is equal to indexEnd
, substring()
returns an empty stringindexStart
is greater than indexEnd
, the substring()
swaps the two argumentsstring.length
is treated as if it were string.length
NaN
is treated as if it were 0The Boolean comparison operators work on strings. To see if two strings are equal:
Other comparison operations are useful for putting words in lexicographical
order:
if (word < "banana") {
console.log("Your word, " + word + ", comes before banana.");
}
else if (word > "banana") {
console.log("Your word, " + word + ", comes after banana.");
}
else {
console.log("Yes, we have no bananas!");
}
Keep in mind that Javascript string comparisons are case sensitive, so, for exampe “Ape” does not equal “ape”. A common way to address the problem of case is to convert strings to a standard format, such as all lowercase, before performing the comparison. So:
It is tempting to use the []
operator on the left side of an assignment, with the intention of changing a character in a string. For example:
At best, the assignment on line 2 above will have no effect. If you run the code in strict mode, you will get an error.
Strings are immutable, which means you can’t change an existing string. The best you can do is create a new string that is a variation on the original:
let greeting = "Hello, world!";
let newGreeting = "J" + greeting.substring(1);
console.log(newGreeting);
The solution here is to concatenate a new first letter onto a slice of greeting
. This operation has no effect on the original string.
Javascript provides string functions that let us find substrings within a string. includes
returns a Boolean true
or false
to indicate if the substring exists in the string.
⠕ let s = "apple";
⠕ s.includes("p");
=> true
⠕ s.includes("i");
=> false
⠕ s.includes("app");
=> true
⠕ s.includes("App"); // false because case doesn't match
=> false
Note that a string is a substring of itself, and the empty string is a substring of any other string. (Also note that computer scientists like to think about these edge cases quite carefully!)
Combining includes
with string concatenation using +=
, we can remove the vowels from a string:
function removeVowels(s) {
let vowels = "aeiou";
let sansVowels = "";
for (let i = 0; i < s.length; i++) {
let c = s[i].toLowerCase();
if(!vowels.includes(c)) {
sansVowels += s[i];
}
}
return sansVowels;
}
console.log(removeVowels("CompSci")); // CmpSc
console.log(removeVowels("A dark and stormy night.")); // drk nd strmy nght.
Check out this function on repl.it
This short function uses several of the techniques and patterns we have previously seen. We use a finite for
loop to iterate up to s.length
to traverse the string. We convert c
to lowercase for the comparison where we test for inclusion in vowels
. We create an empty string, sansVowels
that accumulates the non-vowel characters that we find.
find
functionWhat does the following function do?
// Find and return the index of ch in strng.
// Return -1 if ch does not occur in strng.
function find(strng, ch) {
for(let i = 0; i < strng.length; i++) {
if(strng[i] === ch) {
return i;
}
}
return -1;
}
test(find("Compsci", "p") == 3)
test(find("Compsci", "C") == 0)
test(find("Compsci", "i") == 6)
test(find("Compsci", "x") == -1)
In a sense, find
is the opposite of the indexing operator. Instead of taking an index and extracting the corresponding character, it takes a character and finds the index where that character appears. If the character is not found, the function returns -1
.
This is another example where we see a return
statement inside a loop. If strng[i] === ch
, the function returns immediately, breaking out of the loop prematurely.
If the character doesn’t appear in the string, then the program exits the loop normally and returns -1
.
This pattern of computation is sometimes called a eureka traversal or short-circuit evaluation, because as soon as we find what we are looking for, we can cry “Eureka!”, take the short-circuit, and stop looking.
The following program counts the number of times the letter a
appears in a string, and is another example of the counter pattern.
To find the locations of the second or third occurrence of a character in a string, we can modify the find
function, adding a third parameter for the starting position in the search string:
function find2(strng, ch, start) {
let ix = start;
while (ix < strng.length) {
if (strng[ix] == ch) {
return ix;
}
ix++;
}
return -1
}
console.log(find2("banana", "a", 2) == 3);
The call find2("banana", "a", 2)
now returns 3
, the index of the first occurrence of “a” in “banana” starting the search at index 2. What does find2("banana", "n", 3)
return? If you said, 4, there is a good chance you understand how find2
works.
Better still, we can combine find
and find2
using a default parameter:
function find(strng, ch, start = 0) {
let ix = start;
while (ix < strng.length) {
if (strng[ix] == ch) {
return ix;
}
ix++;
}
return -1
}
When a function has an optional default parameter, the caller may provide a matching argument. If the third argument is provided to find
, it gets assigned to start
. But if the caller leaves the argument out, then start is given a default value indicated by the assignment start = 0
in the function header.
So the call find("banana", "a", 2)
to this version of find
behaves just like find2
, while in the call find("banana", "a")
, start
will be set to the default value of 0
.
Adding another optional parameter to find
makes it search from a starting position, up to but not including the end position:
function find(strng, ch, start = 0, end = null) {
let ix = start;
end = end || strng.length;
while (ix < end) {
if (strng[ix] == ch) {
return ix;
}
ix++;
}
return -1
}
The optional value for end
is interesting: we give it a default value null
if the caller does not supply any argument. In the body of the function we test what end
is, and if the caller did not supply any argument, we reassign end
to be the length of the string. If the caller has supplied an argument for end
, however, the caller’s value will be used in the loop.
On line 3 we see a new use of the logical ||
operator in the assignment expression, end = end || strng.length;
. We re-assign end
the value of end
(i.e. it doesn’t change) if end
is truthy value, or we assign end
a default value, of strng.length
. Because of the way Javascripts logical operators and truthiness work, they can be useful for assignment. As you read more Javascript code online and in books, you will see many examples of code that use the ||
operator to assign default values.
Here are some test cases that should print true
:
indexOf
methodWe wrote our own find
function above, but Javascript’s string object includes its own version of find
called indexOf
.
You can see how it works in the MDN docs.
let ss = "Javascript strings have some interesting methods.";
console.log(ss.indexOf("s") === 4);
console.log(ss.indexOf("s", 11) === 11);
console.log(ss.indexOf("s", 12) === 17);
console.log(ss.indexOf("s", 12, 17) === 17); // indexOf ignores the argument to end
console.log(ss.indexOf(".") === ss.length - 1);
indexOf
does not have the optional end
paremeter. It always seachers until the end of the string. In the case ss.indexOf("s", 12, 17) === 17
it does not create an error to pass in the third argument, however it is ignored entirely.
The built-in indexOf
method is more general than our version. It can find substrings, not just single characters:
Usually we’d prefer to use the methods that Javascript provides rather than reinvent our own equivalents. But many of the built-in functions and methods make good teaching exercises, and the underlying techniques you learn are your building blocks to becoming a proficient programmer.
split
methodOne of the most useful methods on strings is the split
method: it splits a single multi-word string into an array of individual words (“substrings”). The first parameter of split
specifies the character or substring (or regular expression, we’ll see later) to be used to break the string into words. In the example below, we split ss
into words using a single space ' '
;
We’ll often work with strings that contain punctuation, or tab and newline characters, especially, as we’ll see in a future chapter, when we read our text from files or from the Internet. But if we’re writing a program, say, to count word frequencies or check the spelling of each word, we’d prefer to strip off these unwanted characters.
We’ll show just one example of how to strip punctuation from a string. Remember that strings are immutable, so we cannot change the string with the punctuation — we need to traverse the original string and create a new string, omitting any punctuation:
function removePunctuation(s) {
let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
let cleanString = "";
for (let i = 0; i < s.length; i++) {
if (punctuation.indexOf(s[i]) === -1) {
cleanString += s[i];
}
}
return cleanString;
}
Composing together this function and the split
method from the previous section makes a useful combination — we’ll clean out the punctuation, and split
will clean out the newlines and tabs while turning the string into a list of words:
let text = `Born two centuries ago, Ada Lovelace was a pioneer of computer
science. She took part in writing the first published program and was a
computing visionary, recognizing for the first time that computers could do
much more than just calculations!`;
let words = removePunctuation(text).toLowerCase().split(/\s/);
console.log(words);
The output:
[ 'born',
'two',
'centuries',
'ago',
'ada',
'lovelace',
...
'much',
'more',
'than',
'just',
'calculations' ]
Careful readers will have noticed a new syntax for the argument to split
, above. /\s/
is a Javascript regular expression (aka regex) which splits the string on any whitespace — regular spaces, tabs, and newlines. Regular expressions are powerful patterns for matching (and replacing) substrings in strings. Many of the Javascript string methods accept either plain strings are regular expressions as arguments. Regular expressions are beyond the scope of this chapter, but we will demonstrate a few useful examples. MDN offers a guide to using Regular Expressions in Javascript.
There are other useful string methods, but this book isn’t intended to be a reference manual. On the other hand, the Mozilla Developers Network is an excellent reference. You can see all of the String methods here <https://developer.mozilla.org/en- US/docs/Web/JavaScript/Reference/Global_Objects/String>.
Template strings are the easiest and most powerful way to format strings in Javascript. We have already seen how we can use them with the backtick (```) for multiline strings.
Template strings allow us to embed Javascript expressions in a string without using string concatenation. Here are some examples:
let s1 = `The Javascript value of π from the math library is ${Math.PI}`;
console.log(s1);
let name = "Alice";
let age = 10;
let s2 = `I am ${name} and I am ${age} years old.`;
console.log(s2);
let n1 = 4;
let n2 = 5;
s3 = `2**10 = ${2**10} and ${n1} * ${n2} = ${n1 * n2}`;
console.log(s3);
The Javascript value of π from the math library is 3.141592653589793
I am Alice and I am 10 years old.
2**10 = 1024 and 4 * 5 = 20
The template strings resolve any Javascript expression inside the ${}
placeholders to a string and then concatenate the template string into a single string. They can make our code both easier to read and easier to write. You can learn more about template strings from the docs.
A data type in which the values are made up of components, or elements, that are themselves values.
The value given to an optional parameter if no argument for it is provided in the function call.
Use of the dot operator, .
, to access methods and properties of an object.
A data value which cannot be modified. Assignments to elements or slices (sub-parts) of immutable values cause a runtime error.
A variable or value used to select a member of an ordered collection, such as a character from a string, or an element from a list.
[]
)Access a single character in a string using its position (starting from 0). Example: "This"[2]
evaluates to "i"
.
string.length
)Returns the number of characters in a string. Example: "happy".length
evaluates to 5
.
A data value which can be modified. The types of all mutable values are compound types. Lists and dictionaries are mutable; strings and tuples are not.
A parameter written in a function header with an assignment to a default value which it will receive if no corresponding argument is given for it in the function call.
A pattern expressed using the regular expression language to find a substring within string.
A style of programming that shortcuts extra work as soon as the outcome is know with certainty. In this chapter our find
function returned as soon as it found what it was looking for; it didn’t traverse all the rest of the items in the string.
A part of a string (substring) specified by a range of indices. More generally, a subsequence of any sequence type in Javascript can be created using the substring
method of string: "testing".substring(0,4)
.
To iterate through the elements of a collection, performing a similar operation on each.
Any of the characters that move the cursor without printing visible characters. Whitespace in Javascript can be represented by the regular expression /\s/
.
Count vowels. Write a function countVowels(str)
that returns the number of vowels in the string str
.
Quack. Modify:
let prefixes = "JKLMNOPQ";
let suffix = "ack";
for (let i = 0; i < prefixes.length; i++) {
console.log(prefixes[i] + suffix);
}
so that Ouack
and Quack
are spelled correctly.
Count substring Write a function that counts how many times a substring occurs in a string. Example: count("an", "banana") === 2
.
Remove letter. Write a function that removes all occurrences of a given letter from a string: removeLetter("cat", "a") === "ct"
.
Reverse. Write a function reverse(text)
that returns text
with the letters reversed. So, reverse("happy") === "yppah"
Palindrome. Write a function isPalindrome(text)
that returns true
if text
is a palindrome, or false
if it is not. A string is a palindrome if it reads the same forwards and backwards. (Hint: use your reverse
function to make this easy!)
A array is an ordered collection of values. The values that make up an array are called its elements, or its items.
We will use the term element
or item
to mean the same thing. Arrays are similar to strings, which are ordered collections of characters, except that the elements of an array can be of any type. Arrays and strings — and other collections that maintain the order of their items — are called sequences or lists.
There are several ways to create a new array; the simplest is to enclose the elements in square brackets ([
and ]
):
The first array contains four numbers. The second contains an array of three strings. The third is an empty array — it’s waiting for us to add elements. The elements of an array don’t have to be the same type. The following array contains a string, a number, and (amazingly) another array:
An array within another array is said to be nested.
We have already seen that we can assign array values to variables or pass arrays as parameters to functions:
The syntax for accessing the elements of an array is the same as the syntax for accessing the characters of a string — the index operator: []
(not to be confused with an empty array). The expression inside the brackets specifies the index. Remember that the indices start at 0 and can be integers up to length - 1
:
Any expression evaluating to an integer can be used as an index:
If you try to access an element that does not exist, Javascript returns undefined
:
If you assign a value to an element that does not exist, Javascript will add the value to the array at that index, and create empty elements in the intervening indices.
It is common to use a loop variable as a list index.
let horsemen = ["war", "famine", "pestilence", "death"];
for (let i = 0; i < horsemen.length; i++) {
console.log(horsemen[i]);
}
Each time through the loop, the variable i
is used as an index into the array, printing the i
’th element. This pattern of computation is called a array traversal.
Like strings, Javascript arrays have a length
property that tells us how many items are in the array. When we use i < horsemen.length
as the loop condition, our for
loop stops when it accesses the last element of the array.
Javascript arrays have an includes
method which returns a Boolean true
or false
to indicate membership of an item in an array.
The array concat
method combines two arrays into a new array by concatenating an array to the end of another array:
Notice that a
and b
remain unchanged;
push
adds elements to the end of an array and returns the new length of the array. You can pass more than one argument to push
and it will create a new element at the end of the array for each parameter.
⠕ a
=> [ 1, 2, 3 ]
⠕ a.push(4);
=> 4
⠕ a
[ 1, 2, 3, 4 ]
⠕ a.push(3, 2, 1);
=> 7
⠕ a
[ 1, 2, 3, 4, 3, 2, 1 ]
pop()
returns the last element in the array and removes that element from the array. When pop
is called on an empty array, undefined
is returned.
shift
and unshift
are equivalent to pop
and push
except they work on the beginning of an array. a.unshift(-1, 0)
puts -1
in the 0’th element and 0
in the 1’th element. shift
returns the 0’th element of the array and removes it from the array. All other elements are shifted to the left. like pop
, shift
returns undefined
when called on an empty array.
There are many other useful array methods that you can read about in the documentation.
The slice()
method of an array returns a new sub-array. slice
is similar to the substring()
method of strings.
⠕ let t = ["a", "b", "c", "d", "e", "f"];
⠕ t.slice(1, 3);
=> [ 'b', 'c' ]
⠕ t.slice(3);
=> [ 'd', 'e', 'f' ]
⠕ t.slice();
[ 'a', 'b', 'c', 'd', 'e', 'f' ]
The begin
and end
arguments are optional. If only begin
is supplied, as in t.slice(3);
, slice
returns all elements from begin
to the end of the array. If no arguments are provided (t.slice()
) a copy of the entire array is returned.
Unlike strings, arrays are mutable, which means we can change their elements. Using the index operator on the left side of an assignment, we can update one of the elements:
⠕ let fruit = ["banana", "apple", "quince"];
⠕ fruit[0] = "pear";
⠕ fruit[2] = "orange";
⠕ fruit
[ 'pear', 'apple', 'orange' ]
The bracket operator applied to an array can appear anywhere in an expression. When it appears on the left side of an assignment, it changes one of the elements in the array, so the first element of fruit
has been changed from "banana"
to "pear"
, and the last from "quince"
to "orange"
. An assignment to an element of an array is called item assignment. Item assignment does not work for strings:
Javascript just ignores the illegal assignment and we see that myString
remains unchanged. This is not the case for arrays:
⠕ let myCharArray = ["T", "E", "S", "T"];
⠕ myCharArray[2] = "X";
⠕ myCharArray
=> [ 'T', 'E', 'X', 'T' ]
With the splice
method (not to be confused with slice
), we can update, insert, or delete multiple array elements with a single function call.
shift
and pop
remove single items from the beginning or end of an array. splice
is a more flexible (and complicated) method that can return and delete items from any array index, as well as insert or replace multiple items. splice
can operate on more than one element. Here are a few ways to use splice
. The syntax for splice
is splice(index, numDelete, newItems...)
where index
indicates where the deletion or insertion should begin, numDelete
(optionally) indicates how many items to remove. The remaining newItems
can be any number of arguments to be inserted in the array at index
.
Delete multiple items from an index:
⠕ let chars = ["a", "b", "c", "d", "e", "f"];
⠕ let removedChars = chars.splice(1, 2);
⠕ removedChars
=> [ 'b', 'c' ]
⠕ chars
=> [ 'a', 'd', 'e', 'f' ]
Because the numDelete
argument is zero, this inserts items in the middle of an array:
⠕ let numbers = [10, 20, 30, 40];
⠕ numbers.splice(2, 0, 21, 22);
⠕ numbers
=> [ 10, 20, 21, 22, 30, 40 ]
By passing in the number of items to delete, we can replace multiple array elements with splice
:
After we execute these assignment statements
we know that a
and b
will refer to a string object with the letters "banana"
. But we don’t know yet whether they point to the same string object.
There are two possible ways the Javascript interpreter could arrange its memory:
In one case, a
and b
refer to two different objects that have the same value. In the second case, they refer to the same object.
When we use the equality operator on strings, tells us if they hold the same value:
We cannot tell if they refere to the same object or not. Since strings are immutable, Javascript can optimize resources by making two names that refer to the same string value refer to the same object.
This is not the case with arrays:
The state snapshot here looks like this:
a
and b
have the same value but do not refer to the same object.
Since variables refer to objects, if we assign one variable to another, both variables refer to the same object:
In this case, the state snapshot looks like this:
Because the same array has two different names, a
and b
, we say that it is aliased. Changes made with one alias affect the other:
Although this behavior can be useful, it is sometimes unexpected or undesirable. In general, it is safer to avoid aliasing when you are working with mutable objects (i.e. arrays at this point in our textbook, but we’ll meet more mutable objects as we cover classes and objects). Of course, for immutable objects (i.e. strings), there’s no problem — it is just not possible to change something and get a surprise when you access an alias name. That’s why Javascript is free to alias strings (and any other immutable kinds of data) when it sees an opportunity to economize.
If we want to modify an array and also keep a copy of the original, we need to be able to make a copy of the array itself, not just the reference. This process is sometimes called cloning, to avoid the ambiguity of the word copy.
The easiest way to clone an array is to call the slice
method with zero arguments:
Calling slice
always creates a new array. In this case the slice happens to consist of the whole array. So now the relationship is like this:
Now we are free to make changes to b
without worrying that we’ll inadvertently be changing a
:
Passing an array as an argument actually passes a reference to the array, not a copy or clone of the array. So parameter passing creates an alias for you: the caller has one variable referencing the array, and the called function has an alias, but there is only one underlying array object.
For example, the function below takes an array as an argument and multiplies each element in the array by 2:
/**
* Overwrite each element in `t` with double its value
*/
function doubleStuff (t) {
for (let i = 0; i < t.length; i++) {
t[i] = t[i] * 2;
}
}
If we add the following onto our script:
When we run it we’ll get:
In the function above, the parameter t
and the variable things
are aliases for the same object. So changes to t
or things
are reflected in both t
and things
.
Functions which take arrays as arguments and change them during execution are called modifiers and the changes they make are called side effects.
A pure function does not produce side effects. It communicates with the calling program only through parameters, which it does not modify, and a return value. Here is doubleStuff
written as a pure function:
/**
* Double the value of each element in array `t`
* and return a new array with the doubled values
*/
function doubleStuff (t) {
let tt = [];
for (let i = 0; i < t.length; i++) {
tt[i] = t[i] * 2;
}
return tt;
}
This version of doubleStuff
does not change its arguments:
⠕ let things = [2, 5, 9];
⠕ let xs = doubleStuff(things);
⠕ things
=> [ 2, 5, 9 ]
⠕ xs
=> [ 4, 10, 18 ]
An early rule we saw for assignment said “first evaluate the right hand side, then assign the resulting value to the variable”. So it is quite safe to assign the function result to the same variable that was passed to the function:
The pure version of doubleStuff
above made use of an important pattern for your toolbox. Whenever you need to write a function that creates and returns an array, the pattern is usually:
initialize a result variable to be an empty array
loop
create a new element
append it to result
return the result
Let us show another use of this pattern. Assume you already have a function isPrime(x)
that can test if x is prime. Write a function to return an array of all prime numbers less than n:
Two of the most useful methods on strings involve conversion to and from arrays of substrings.
The split
method (which we’ve already seen) breaks a string into an array of words. We have been using the simple regular expression /\s/
to split strings at the whitespace pattern.
⠕ let song = "The rain in Spain...";
⠕ let words = song.split(/\s/);
⠕ words
=> [ 'The', 'rain', 'in', 'Spain...' ]
In this example the whitespace regex is the delimiter — the toke used to specify which string to use as the boundary marker between substrings. The following example uses the string ai
as the delimiter:
Notice that the delimiter doesn’t appear in the result.
The inverse of the split
method is join
. You choose a desired separator string, (often called the glue) and join the array with the glue between each of the elements:
The array that you glue together (words
in this example) is not modified. Also, as these next examples show, you can use empty glue or multi-character strings as glue:
Using thesplit
method of strings, we can write an elegant solution to counting words in a text by looping through the array of words. In the code below we look at section [Martin Luther King Jr’s 1963 “I have a dream …” speech] (https://www.archives.gov/files/press/exhibits/dream-speech.pdf) in order to count the number of times the word dream occurs.
let text = `I say to you today, my friends, so even
though we face the difficulties of today and tomorrow,
I still have a dream. It is a dream deeply rooted in
the American dream. I have a dream that one day this
nation will rise up and live out the true meaning of
its creed: “We hold these truths to be self-evident:
that all men are created equal.” I have a dream that
one day on the red hills of Georgia the sons of former
and the sons of former slave owners will be able to
sit down together at the table of brotherhood.`;
let words = text.split(/\s/);
let counter = 0;
for (let i = 0; i < words.length; i++) {
if (words[i].includes("dream")) {
counter++;
}
}
console.log(`The speech has ${words.length} words.
We found "dream" ${counter} times.`);
code walk through
Play with the code live at https://repl.it/@mcuringa/DreamWordCount
A nested array is an array that appears as an element in another array. In this array, the element with index 3 is a nested array:
If we output the element at index 3, we get:
To extract an element from the nested array, we can proceed in two steps:
Or we can combine them:
Bracket operators evaluate from left to right, so this expression gets the 3’th element of nested
and extracts the 1’th element from it.
Nested arrays are often used to represent matrices. For example, the matrix:
might be represented as:
mx
is an array with three elements, where each element is a row of the matrix. We can select an entire row from the matrix in the usual way:
Or we can extract a single element from the matrix using the double-index form:
The first index selects the row, and the second index selects the column. Although this way of representing matrices is common, it is not the only possibility.
range
function is called the step size. If not specified, it defaults to 1.
Write a function to count how many odd numbers are in an array.
Sum up all the even numbers in an array.
Return an array with all of the negative numbers in an array. Do not modify the original array (i.e. write a pure function).
Sum all the elements in an array up to but not including the first even number. (What if there is no even number?)
Write a function that takes an array of numbers and returns the an array of numbers with the highest and lowest scores removed. Do not modify the original array parameter.
Check out the live code here: https://repl.it/@mcuringa/Array-Examples
filterWord
which takes an array of strings and a word
to filter as arguments and returns a new array with all instances of word
removed. This function should not modify the original array.removeDuplicates
that takes an array and returns a new array with only the unique elements from the original. Hint: they don’t have to be in the same order. Hint hint: remember the includes
method of array.Write a function called combine
that takes 2 arrays of strings as parameters and returns a new array of strings which concatenates the items from the first array with the item from the second. If the array are not equal in length, the new array will end with the item from the longer array. For example:
Write a function called isSorted
which takes an array (of number) as a parameter. It should return true
if the array is already sorted (ascending order) or false
if the array is not sorted.
You can use this function to test your code:
Sentiment Analysis is a technique in computer science text analysis which tries to determine the sentiment or mood expressed in a text. Some “sentiments” are easy to detect. Given the text “I love dogs so much!”, we can confidently say it is positive. “I hate you and never want to talk to you again” is clearly negative. “The car is silver.” would express a neutral sentiment.
Unfortunately, language is often much trickier to classify. Consider, “I have too much homework” (negative) and “I don’t have too much homework” (positive).
For this lab, you are going to write the best sentiment function that you can. It won’t handle all cases, but try to handle as many as you can think of. You should use the string and array functions that we have been working with in the last two chapters.
Your function has a single string parameter, and returns 1
for a positive sentiment, -1
for a negative sentiment, and 0
for a neutral sentiments.
Use this repl to get started: https://repl.it/@mcuringa/Sentiment
You can read the Wikipedia article on sentiment analysis to get a better sense of this lab.
The New York City public school system, run by the NYC Department of Education (DOE). Is the largest public school system in the United States, with over 1 million students in more than 2,000 schools. The schools are tasked with educating a diverse population, with almost 75% of students coming from economically disadvantaged families.
It is home to some of the best schools in the country, with selective schools like Stuyvesant High School and Bronx Science, but also faces chronically low performance in many of its schools. The schools face criticism and controversy surrounding a variety of issues. Black and Latinx students face the highest levels of segregation in the country; contributing to significant inequity within schools in New York City and with other New York State public schools in the wealthy suburbs that ring the city.
Some of the initiatives meant to address these challenges have proven as controversial as the underlying problems. Charter schools — public schools with fewer restrictions, operated by private organizations — were introduced to address some of these issues by providing “school choice.” Charters, however, have been criticized for failing to meet students with disabilities and English Language Learners; they are seen by some as more of an attack on public unions than as education initiatives, and as fundamentally flawed by pitting schools against each other.
Recent desegregation attempts in NYC’s top performing schools has been criticized for unfairly impacting Asian American students, who are well represented in the top schools, but themselves often come from poorer, immigrant families.
Data analysis, data science, and data-driven decision making has been gaining momentum as one tool in improving the NYC educational system through greater insight and accountability. As part of a larger open data initiative, the City publishes a lot of data about its schools and its students. Among other data, the City releases school-level test results for Math and English Language Arts (ELA). These results can be analyzed for different demographic groups, including by race/ethnicity, gender, income, students with disabilities, and English Language Learners. The data analyzed in this case study reports on the ELA test results for the years 2013-2017, for “all students”, at the school level. The results reported at New York State statewide assessments given in grades 3-8 (to, generally, students age 8-13). The data file (ela-all-2013_18.js
) is based on data released on the NYC DOE website. The Math and ELA exams are aligned to the Common Core standards. The ELA exam includes reading passages and multiple choice, short answer, and extended answer (essay) responses. Student results are categorized as a 1, 2, 3, or 4 where 1 is the lowest result and 4 is the highest result. Students scoring 3 or 4 are considered proficient in the subject area.
Lastly, while the tests are administered to every student in the public schools there has been a significant resistant to the tests in some schools and districts, statewide, in the form of “opting out” where students and parents choose not to participate in the testing due to concerns about how test results are used to punish schools, teachers, and students; questions over the validity of the measures; and beliefs that high stakes testing corrupts learning and siphons resources from more authentic and valuable educational goals.
The functions annotated in school-report.js
demonstrate some common programming patterns for working with arrays of data, and array traversal — using a for
loop to iterate over each item in an array. Notice that all of the functions that work with the testData
are pure functions. They do not have side effects — they don’t modify any variables outside of the function, only communicating through input function parameters and output with the return statement. To make it easier to understand these pure functions, each function has a comment header which indicates what values and types of data are expected for the function parameters and are returned by the return statement.
The set of reporting functions at the end of program, however, are not pure functions. They provide user output using console.log
. This printing to the console is a side effect of the function.
/**
* File: school-report.js
* Demonstrates common patterns for using for loops
* and arrays, using the NYC school data file and the
* school data object.
*
* Properties of TestResult objects:
*
* - id: string, a unique id that has the school district`, boro,
* and school number, ex: "01M015"
* - name: string, the full school name, ex:"P.S. 015 Roberto Clemente"
* - grade: integer, the grade level for the test results, grades 3-8
* - year: integer, the year the test was taken. The data includes all
* results from 2013-2017
* - numTested: integer, the number of students taking the test at this
* school-grade-year
* - level1: integer, the number of students scoring a one (1), lowest
* - level2: integer, the number of students scoring a two (2)
* - level3: integer, the number of students scoring a three (3)
* - level4: integer, the number of students scoring a four (4), highest
* - district: integer, the NYC DOE school district, districts are smaller
* geographic areas consisting of several schools, with their own, more
* local administration. They are numbered 1-32, with disctrict 75 as a
* citywide special district designed to help students with disabilities
* and special needs
* boro: string, one of the five boroughs:
* Manhattan, Bronx, Brooklyn, Queens, Staten Island
* - avg: float, the average score for this school, for the given year
* and grade level. is NaN if the results are not valid
*
* The functions in this program work on test data stored as an array
* of TestResult data objects. An example of a single object:
* {
* "id": "13K009",
* "name": "P.S. 009 Teunis G. Bergen",
* "grade": 4,
* "year": 2016,
* "numTested": 107,
* "level1": 20,
* "level2": 28,
* "level3": 22,
* "level4": 37,
* "district": 13,
* "boro": "Brooklyn",
* "avg": 2.710280373831776
* },
*/
import testData from "./data/ela-all-2013_18.js";
/**
* totalTested finds the total number of students
* tested in data set `data`
* This is an example of the counter pattern.
* @param data, an array of data objects where each
* objcect _must_ have a property `numTested`
* @return int, the sum of all test takers
*/
function totalTested(data) {
let total = 0;
for (let i = 0; i < data.length; i++) {
total += data[i].numTested;
}
return total;
}
/**
* totalTestedGrade finds the total number of students
* tested in the data set in a give grade level.
* This is an example of the counter pattern
* with a conditional check.
* @param data, an array of data objects where each
* objcect _must_ have the properties
* `numTested` and `grade`
* @param grade, an int in the range 3..8 to indicate
* which grade to search for
* @return int, the total number students of students tested
* in the given grade
*/
function totalTestedGrade(data, grade) {
let total = 0;
for (let i = 0; i < data.length; i++) {
if (data[i].grade === grade) {
total += data[i].numTested;
}
}
return total;
}
/**
* averageTestScores finds the average test scores
* in the dataset `data` for a single `grade`
* in a single test `year`.
* It's a more complex example of a real solution
* programmed using the counter pattern.
* @param data, an array of data objects where each
* objcect _must_ have the properties
* `numTested` and `grade`
* @param grade, an int in the range 3..8 to indicate
* which grade to search for
* @param year, an int
* @return float, the mean average test result
* (in the range 0..4) of all results that match
* the `grade` and `year` criteria
*/
function averageTestScores(data, grade, year) {
let sum = 0;
let numResults = 0;
for (let i = 0; i < data.length; i++) {
let testResults = data[i];
if (testResults.grade === grade
&& testResults.year === year
&& testResults.avg !== NaN) {
sum += testResults.avg;
numResults++;
}
}
return sum / numResults;
}
/**
* findHighestAvgScore searches through
* the test results in `data` and returns
* the test result data object with the highest
* number in the `avg` property.
* @param data, an array of data objects where each
* object _must_ have the property
* `avg` to indicate the average test
* score for that data record
* @return object, the test result data object with
* the highest average score in the dataset
*/
function findHighestAvgScore(data) {
let highestResult = data[0];
for (let i = 1; i < data.length; i++) {
if (data[i].avg > highestResult.avg) {
highestResult = data[i];
}
}
return highestResult;
}
/**
* filterBetterThanLimit returns a sub-array
* based on array `data`, with only the test result
* elements of data where the average test score
* is higher than `limit`.
* @param data, an array of data objects where each
* object _must_ have the property
* `avg` to indicate the average test
* score for that data record
* @param limit, a float in the range 0..4 to indicate
* the lower limit for test results to return
* @return array, an array of test result objects, all of
* which will have a higher average test score
* than `limit`
*/
function filterBetterThanLimit(data, limit) {
let matchingResults = [];
for (let i = 0; i < data.length; i++) {
if (data[i].avg > limit) {
matchingResults.push(data[i]);
}
}
return matchingResults;
}
// _________________________________________________
// ============================= REPORTING FUNCTIONS
// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
function header(title) {
console.log("\n------------------------------------");
console.log(title);
console.log("------------------------------------\n");
}
function reportAveragesForFirstCohort() {
let r13 = averageTestScores(testData, 4, 2013).toFixed(2);
let r14 = averageTestScores(testData, 5, 2014).toFixed(2);
let r15 = averageTestScores(testData, 6, 2015).toFixed(2);
let r16 = averageTestScores(testData, 7, 2016).toFixed(2);
let r17 = averageTestScores(testData, 8, 2017).toFixed(2);
header("TREND OF SCORES FOR 2013 4TH GRADE");
console.log(`
Reporting on the ELA test result trend for the
cohort of who were in 4th grade in 2013.
`);
console.log("Average ELA 2013:", r13);
console.log("Average ELA 2014:", r14);
console.log("Average ELA 2015:", r15);
console.log("Average ELA 2016:", r16);
console.log("Average ELA 2017:", r17);
}
function topPerformers() {
header("TOP PERFORMING SCHOOLS (AVG > 3.9)");
let results = filterBetterThanLimit(testData, 3.9);
for(let i = 0; i < results.length; i++) {
let r = results[i];
console.log(`
${i + 1}. ${r.name}
average score: ${r.avg.toFixed(2)}
${r.numTested} students in grade ${r.grade} in ${r.year}`);
}
}
function main() {
header("SCHOOL TEST DATA REPORT");
console.log("Total students tested:", totalTested(testData));
console.log("Total tested, 4th grade:", totalTestedGrade(testData, 4));
header("BEST TEST RESULTS");
let highest = findHighestAvgScore(testData);
console.log(`
The highest average score results were for the students
in grade ${highest.grade} at ${highest.name} in ${highest.year}.
${highest.numTested} students had an average score of ${highest.avg}.
`);
reportAveragesForFirstCohort();
topPerformers();
header("REPORT COMPLETE");
}
main();
Fork the SchoolData repl https://repl.it/@mcuringa/SchoolData to complete these exrcises.
findHighestAvgScore
so that it has a second parameter, minStudents
.minStudents
.findByBorough(data, boro)
that returns an array of test results for schools in borough boro
.reportAveragesForFirstCohort
as an example for the type of report.Find the 5 best test results in the data set. Return an array of the test result objects for these schools. There should be a minimum number of students tested to qualify (specified by a function parameter).
Write a function schoolAverages(data)
that returns an array of test report objects with exactly one object for each school. grade
and year
should be set to null
because they don’t make sense in this summary. numTested
should be the total students tested for that school in the given data
. level[1-4]
should represent the total students score at that level across years and grades. And avg
should have the average (1..4) score for all years and grades.
Write a function boroAverages(data, year)
that returns an array of objects. The objects must have the following properties:
avg
: the average test score for the borough (1..4)numTested
: the total of all students testedboro
: the name of the boroughSo far we have looked at sequential data types — strings and arrays — which use integers as indices to access the values they contain.
Maps are yet another kind of compound type. It is conventional to use Objects as the mapping type in Javascript, as we have seen in the School Data case study. Javascript also offers a built-in [Map data type] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), but we will stick with the Object convention for this chapter.
Mapped data structures map keys to values. Values can be any type (heterogeneous), just like the elements of an array. Sometimes maps are called associative arrays since they associate a key with a value. When using objects as maps (as we will for most of this chapter), keys should be strings. The Map
object in Javascript allows keys to be of any type.
As an example, we will create a map to translate English words into Spanish. For this map, the keys are strings.
One way to create a map is to start with the empty object and add key:value pairs. The empty object is denoted by {}
:
The first assignment creates a map named eng2sp
; the other assignments add new key:value pairs to the map. We can print the current value of the map in the usual way:
The key:value pairs of the map are separated by commas. Each pair contains a key and a value separated by a colon.
Another way to create a map is to provide a list of key:value pairs using the same syntax as the previous output:
It doesn’t matter what order we write the pairs. The values in a map are accessed with keys, not with indices, so there is no need to care about ordering.
Here is how we use a key to look up the corresponding value:
The key "two"
yields the value "dos"
.
Arrays and strings have been called sequences, because their items occur in order. The map is the first compound type that we’ve seen that is not a sequence, so we can’t index or slice a map.
The delete
statement removes a key:value pair from a map. For example, the following map contains the names of various fruits and the number of each fruit in stock:
⠕ let inventory = {"apples": 430, "bananas": 312, "oranges": 525, "pears": 217};
⠕ console.log(inventory);
{ apples: 430, bananas: 312, oranges: 525, pears: 217 }
If someone buys all of the pears, we can remove the entry from the map:
Or if we’re expecting more pears soon, we might just change the value associated with pears:
⠕ inventory["pears"] = 0;
⠕ console.log(inventory);
{ apples: 430, bananas: 312, oranges: 525, pears: 0 }
A new shipment of bananas arriving could be handled like this:
The built in Object
library contains many useful functions for working with objects as map datatypes.
We can use Object.keys()
to return an array of all of the keys in a map. Using the keys, we can iterate through the keys and values in our map.
let eng2sp = {"one": "uno", "two": "dos", "three": "tres"};
let keys = Object.keys(eng2sp);
for (let i = 0; i < keys.length; i++) {
let k = keys[i];
console.log("Got key", k, "which maps to value", eng2sp[k]);
}
console.log(keys);
This produces this output:
Got key one which maps to value uno
Got key two which maps to value dos
Got key three which maps to value tres
[ 'one', 'two', 'three' ]
It is so common to iterate over the keys in a map that Javascript provides a special syntax for this loop.
The Object.values()
function is similar to keys()
; it returns an array containing all of the map’s objects:
The Object.entries()
function returns an array of key:value pair arrays.
Combining Object.entries()
with a Javascript syntax for destructuring assignment, we can easily iterate over the keys and values of a map.
This produces:
This simple example introduces two new Javascript structures. Destructuring takes the values of an array on the right hand of the assignment operator and assigns them to multiple individual variables on the left hand side of the operation. let [a, b] = [1, 2]
creates two new variables, a
and b
with the values 1
and 2
respectively.
We also use the special for…of syntax for this loop. This is a compact form of our usual array iteration loop that doesn’t require (or afford) a loop index. Other than iterating through key:value pairs of maps, we will use the standard for loop for array iteration. You can read more about for…of in the docs.
If we try to access a key that isn’t in our map, Javascript yields the special undefined
value. Javascript offers the in
operator specifically to allow us to test if a key exists in a map. in
will always return a boolean result (true
or false
) indicating the existence of a key.
Later in the chapter we will see some examples where we use this technique to determine if we should update an existing entry in a map or create a new entry.
Have a look at this video if you want to see some of the map basics in action.
As in the case of arrays, because maps are mutable, we need to be aware of aliasing. Whenever two variables refer to the same object, changes to one affect the other.
If we want to modify a map and keep a copy of the original, use the copy
method. For example, opposites
is a map that contains pairs of opposites:
⠕ let opposites = {"up": "down", "right": "wrong", "yes": "no"};
⠕ let alias = opposites;
⠕ let copy = Object.assign(opposites); // a shallow copy
alias
and opposites
refer to the same object; copy
refers to a fresh copy of the same map. If we modify alias
, opposites
is also changed:
If we modify copy
, opposites
is unchanged:
In the exercises in in the Strings chapter, we wrote a function that counted the number of occurrences of vowels in a string. A more general version of this problem is to form a frequency table of the letters in the string, that is, how many times each letter appears.
Such a frequency table might be useful for compressing a text file. Because different letters appear with different frequencies, we can compress a file by using shorter codes for common letters and longer codes for letters that appear less frequently.
Maps provide an elegant way to generate a frequency table:
let letterCounts = {};
let word = "Mississippi";
word = word.toLowerCase();
for (let i = 0; i < word.length; i++) {
let letter = word[i];
if (letter in letterCounts) {
letterCounts[letter]++;
}
else {
letterCounts[letter] = 1;
}
}
console.log(letterCounts);
Outputs:
We start with an empty map. For each letter in the string, we find the current count (possibly zero) and increment it. At the end, the map contains pairs of letters and their frequencies. We use a Boolean if
/else
to determine if the key exists in our map. _If the key is already in
the map we increment the count, else we assign an initial value of 1 to that key. Note that we call toLowerCase()
on our string because keys, as you should expect, are case-sensitive.
It might be more appealing to display the frequency table in alphabetical order. We can do that with the Object.keys()
function in the Object library and then calling sort()
on our array of keys. We can add this code to our example above in order to print out the frequency map of letters in alphabetical order.
let keys = Object.keys(letterCounts);
keys.sort();
for(let i = 0; i < keys.length; i++) {
let letter = keys[i];
console.log(`${letter}: ${letterCounts[letter]}`);
}
See the interactive example online at https://repl.it/@mcuringa/MapLetterFrequency.
A GUI or graphical user interface is the main way that most of us interact with computers most of the time. The standards for graphical computing are well known: clicking buttons and icons, scrolling windows, reading images and text, etc. When we created our Turtle programs, we were programming GUIs.
Most of the examples in this text, however have used text interfaces or Command Line Interfaces (CLI). With a CLI, the user receives computer output through text on the screen, and sends input to the program by entering text. In this chapter, we introduce a Javascript-based library for creating GUIs web applications.
There are three computer languages that form the base for any web page. They are HTML, CSS, and Javascript. Hypertext markup language (HTML) describes the type of content on a web page. HTML describes many different types of content. You are familiar with most of them: - text: titles, paragraphs, lists, etc - media: images, videos, audio, animation graphics - links: that allow navigation to different areas of the web - form elements: buttons, input boxes, checkboxes, and other elements that users to input information - tables: that display grids of information
Other HTML elements are more structural, they divide web pages into sections (like headers and footers).
Cascading style sheets (CSS), control the layout of the content described by the HTML. CSS can be used to change the font or text size, add background images to elements, space and size different containers, and, generally create useful and aesthetic interfaces. A single HTML document can work with different CSS styles, allowing it to adapt to different use cases. One set of styles can control layout for mobile phones, another for laptops, and yet another for print.
Javascript is the only programming language of the three languages discussed here. It’s the only of the group that uses variables and controls the flow of execution with loops and Boolean conditions. At this point in the book, we are familiar with many of the programming aspects of Javascript. For the web, Javascript can interact with user input, and dynamically change the content and HTML structure on a page. While some, small websites are static — meaning that all of the content and HTML is written by hand in a text editor like the one in repl.it — most website pull the content from databases or other data files. We have begun to see how Javascript can interact with larger sets of data, now we will see how Javascript can be used to display this data on the web, to create dynamic websites that change when the data is changed.
JS GUI is a Javascript library or module that is being written to accompany the examples in this book. There are many powerful Javascript frameworks, like React (developed by Facebook) and Angular (developed by Google), used by professional software developers to create complex, interactive web applications. However, these professional frameworks aren’t necessarily the best or easiest to start with.
To use JS GUI, you need the following:
index.html
)jsgui.js
libraryThe following code examples assume such a set-up.
JS GUI provides a series of functions for creating and adding graphical elements to a web page. After importing the library, you can easily start adding elements. The following code creates a web site with the text, Hello, world.
JS GUI provides functions to create titles, (level 1-6), images, and paragraphs of text. New lines can be created with br()
and horizontal rules with hr()
. This code uses titles, text, images, breaks, and rules.
import jsgui from "./lib/jsgui.js";
let catPic = jsgui.img("cat.jpg", "a black cat");
let dogPic = jsgui.img("dog.jpg", "a shaggy Newfoundland dog");
jsgui.add(jsgui.h1("Cats and Dogs"));
jsgui.add(jsgui.p("Do you prefer cats or dogs?"));
jsgui.add(jsgui.h4("Cat people"));
jsgui.add(catPic);
jsgui.add(jsgui.br());
jsgui.add("Some people like cats. Cats are quiet, playful, soft, and sweet.");
jsgui.add(jsgui.br());
jsgui.add(jsgui.hr());
jsgui.add(jsgui.br());
jsgui.add(jsgui.h4("Dog people"));
jsgui.add(dogPic);
jsgui.add(jsgui.br());
jsgui.add(`Some people like dogs. Dogs are loyal friends and
companions, sad to see you leave and ecstatic when you get home.``);
The img(url, alt)
function has two parameters, the first is the URL of the image. In this case, it’s in the same location as our code, so we only need the file name: cat.jpg
. If it was on another server, we could put the full URL, something like https://www.example.com/img/cat.jpg
. THe alt
parameter is a short text description of the image that makes the web more useful for users with disabilities, such as visual impairment. For the catPic
and dogPic
we save the image into a variable before calling add()
to place it on the web page.
Th rest of the html elements in this code example are composed directly with the add()
function. h1()
and h4()
create a level 1 header and level 4 header respectively. p()
creates a paragraph of text. br()
is a line break that forces a new line. Vertical whitespace in HTML is (usually) ignored when the web browser lays out a page. hr()
creates a horizontal rule (line) across the page.
JS GUI has some helper functions that makes it easy to display mapped data and arrays of data. For the key-value pairs in a map, JS GUI uses the HTML definition list element. A definition list (<dl>
) describes contents that have a term and a definition. The dl()
function maps the keys to terms and the values to definitions.
// ...
let pet = {
name: "Sparkle Swan Fireworks",
type: "Cat",
age: "3",
breed: "American Short Hair"
}
jsgui.add(jsgui.dl(pet));
Arrays of data can be quickly laid out as tables. The example below uses an array of arrays, where each element of animalsOnScreen
is an array. jsgui.table(data, header)
takes the data as the first argument. If an array is passed for the header
argument, table()
will use that data to make bold column headers. Although not used here, data
can also be an array of data maps. If this is the case, only the values
from the map are displayed in the table rows.
// ...
let animalsOnScreen = [
["Name", "Type", "Breed", "Film/Show"],
["Rin Tin Tin", "Dog", "German Shepherd", "Various"],
["Lassie", "Dog", "Collie", "Lassie"],
["Mrs. Norris", "Cat", "Maine Coon", "Harry Potter"],
["Flipper", "Dolphin", "bottlenose", "Flipper"]
];
let table = jsgui.table(animalsOnScreen.slice(1), animalsOnScreen[0]);
jsgui.add(table);
This example creates an array with 4 columns and 5 elements. Since the 0-th element contains the header data, slice(1)
is used to exclude that row when table()
is called, and the array at animalsOnScreen[0]
is passed as the header
argument.
Web pages, like software programs, can be made more flexible using the decomposition strategy of breaking the problem (the page layout) into smaller parts. HTML offers a number of block elements that contain other elements (including blocks). Using Javascript, we can write functions that return a group of elements in one of these containers. We can make our code more abstract, where functions can use the same layout for different content. Alternatively, we can make functions that are re-usable, such as returning headers, footers, and navigation elements that can be used across several web pages.
The examples in this code listing use many, but not all of the layout features of JS GUI.
/**
* index.js is the default script for the JSGUI Demo Site
* It demonstrates some of the main layout functions included
* in the js gui package.
*/
import jsgui from "./lib/jsgui.js";
/**
* create a panel about an animal with a title, img, and caption
*/
function animalDetail(title, img, text) {
let container = jsgui.div();
jsgui.append(container, jsgui.h4(title), img, jsgui.br(), text);
return container;
}
/**
* create a section with all of the elements for Cats or Dogs
*/
function catsOrDogs() {
let sec = jsgui.section();
let catPic = jsgui.img("cat.jpg", "a black cat");
let dogPic = jsgui.img("dog.jpg", "a shaggy Newfoundland dog");
let catPeople = animalDetail("Cat people", catPic,
"Some people like cats. Cats are quiet, playful, soft, and sweet.");
let dogPeople = animalDetail("Dog people", dogPic,
"Some people like dogs: loyal friends and quick with a trick.");
let grid = jsgui.grid(2);
jsgui.addToGrid(grid, 1, catPeople);
jsgui.addToGrid(grid, 2, dogPeople);
jsgui.append(sec,
jsgui.h2("Cats and Dogs"),
jsgui.p("Do you prefer cats or dogs?"),
grid,
jsgui.hr());
return sec;
}
/**
* create the Animals on Screen section
*/
function animalActors() {
let sec = jsgui.section();
let animalsOnScreen = [
["Name", "Type", "Breed", "Film/Show"],
["Rin Tin Tin", "Dog", "German Shepherd", "Various"],
["Lassie", "Dog", "Collie", "Lassie"],
["Mrs. Norris", "Cat", "Maine Coon", "Harry Potter"],
["Flipper", "Dolphin", "Bottlenose", "Flipper"]
];
let table = jsgui.table(animalsOnScreen.slice(1), animalsOnScreen[0]);
jsgui.append(sec,
jsgui.h2("Animals on the Screen"),
table,
jsgui.hr());
return sec;
}
/**
* create a page header
*/
function header() {
let h = jsgui.header();
jsgui.append(h, jsgui.h1("Animals On Screen"));
jsgui.append(h, jsgui.h5("A demo of the JS GUI library"));
let about = "JS GUI is [Free Open Source Software](https://fsf.org)"
+ "for teaching and learning to program web-based JS guis. "
+ "It was created by Robby Lucia and Matt Curinga to accompany "
+ "the open textbook, _Think JS_. "
+ "[[read online](http://mcuringa.github.com/think-js)] "
+ "[[source](https://github.com/mcuringa/think-js)].";
about = jsgui.md(about);
jsgui.append(h, about);
jsgui.append(h, jsgui.hr());
return h;
}
// start our program
function main() {
jsgui.add(
header(),
catsOrDogs(),
animalActors()
);
}
main();
This code listing steps through the process of making a moderately complex web user interface that reacts to user actions. It presents a list of spells and allows the user to filter the list by checking/unchecking a series of checkbox form elements. The code must keep track of which boxes are checked in the React Object state. The list of spells must update dynamically when changes are detected. This is what we mean by a reactive interface.
import React, { Component } from 'react';
import { Link } from "react-router-dom";
import _ from "lodash";
import { ClassIcon } from "./Icons";
import spells from "./data/spells.json";
import details from "./res/wireframes/spell-detail.png";
class SpellTable extends Component {
constructor(props) {
super(props);
let filters = {
bard: true,
cleric: true,
druid: true,
monk: true,
paladin: true,
ranger: true,
sorcerer: true,
warlock: true,
wizard: true
}
this.state = {
spells: [],
charClassFilters: filters
};
this.toggleFilter = this.toggleFilter.bind(this);
this.clearFilters = this.clearFilters.bind(this);
this.allCharClasses = this.allCharClasses.bind(this);
}
componentWillMount() {
let spellList = _.sortBy(spells, "level_int");
this.setState({ spells: spellList });
}
toggleFilter(charClass) {
let filters = this.state.charClassFilters;
filters[charClass] = !filters[charClass]; //toggle the filter
this.setState({charClassFilters: filters});
}
clearFilters() {
let filters = this.state.charClassFilters;
for(let k in filters) {
filters[k] = false;
}
this.setState({charClassFilters: filters});
}
allCharClasses() {
let filters = this.state.charClassFilters;
for(let k in filters) {
filters[k] = true;
}
this.setState({charClassFilters: filters});
}
render() {
const th = (header, index) => {
return (<th key={index}>{header}</th>)
};
let headers = [
"Spell",
"Level",
"School",
"Casting Classes"
];
headers = headers.map(th);
let filters = this.state.charClassFilters;
let spells = filterSpellsByCharClass(this.state.spells, filters);
const rows = spells.map(SpellRow);
return (
<section id="Spells">
<h2>Spells</h2>
<h5 className="text-primary">
Filter list
<button type="button" className="btn btn-link btn-sm" onClick={this.clearFilters}>[clear]</button>
<button type="button" className="btn btn-link btn-sm" onClick={this.allCharClasses}>[all]</button>
</h5>
<form>
<IconCheckBox onChange={this.toggleFilter} isActive={filters["bard"]} charClass="bard" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["cleric"]} charClass="cleric" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["druid"]} charClass="druid" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["monk"]} charClass="monk" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["paladin"]} charClass="paladin" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["ranger"]} charClass="ranger" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["sorcerer"]} charClass="sorcerer" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["warlock"]} charClass="warlock" />
<IconCheckBox onChange={this.toggleFilter} isActive={filters["wizard"]} charClass="wizard" />
</form>
<table className="table table-hover table-sm">
<thead><tr>{headers}</tr></thead>
<tbody>{rows}</tbody>
</table>
</section>
)
}
}
function filterSpellsByCharClass(spells, classes) {
let t = [];
function isActive(spellCasters) {
for(let i = 0; i < spellCasters.length; i++) {
let charClass = spellCasters[i].toLowerCase();
if(classes[charClass]) {
return true;
}
}
return false;
}
for(let i = 0; i < spells.length; i++) {
const spell = spells[i];
const spellCasters = spell["class"].split(", ");
if(isActive(spellCasters)) {
t.push(spell);
}
}
return t;
}
function IconCheckBox(props) {
function toggle() {
props.onChange(props.charClass);
}
return (
<span key={`toggle_${props.charClass}`} className="form-check form-check-inline">
<input className="form-check-input"
type="checkbox"
checked={props.isActive}
onChange={toggle} />
<ClassIcon charClass={props.charClass} /> {props.charClass}
</span>
)
}
function SpellRow(spell) {
const check = (casterClass) => {
const spellCasters = spell["class"].split(", ");
if (_.includes(spellCasters, casterClass)) {
return (
<abbr title={casterClass}>
<ClassIcon charClass={casterClass.toLowerCase()} />
</abbr>
)
}
return null;
}
const level = spell.level_int || "C";
return (
<tr key={spell._id}>
<td><Link to="/spells/detail">{spell.name}</Link></td>
<td>{level}</td>
<td>{spell.school}</td>
<td>
{check("Bard")}
{check("Cleric")}
{check("Druid")}
{check("Paladin")}
{check("Ranger")}
{check("Sorcerer")}
{check("Warlock")}
{check("Wizard")}
</td>
</tr>
);
}
export { SpellTable };
Online code examples in this book are hosted on https://repl.it. Called “repls”, these source files and programs can be edited and run online, and can be easily forked (remixed) and shared. Several types of repls are available: blank templates for working on practice exercises and labs, solutions to exercise problems, and longer code listings that appear in the book.
We recommend that you make a free account on repl.it to fully work with the examples, however, you can run and edit the programs as an anonymous user. You cannot save your work without an account.
for
By Jeffrey Elkner
This book owes its existence to the collaboration made possible by the Internet and the free software movement. Its three authors—a college professor, a high school teacher, and a professional programmer—never met face to face to work on it, but we have been able to collaborate closely, aided by many other folks who have taken the time and energy to send us their feedback.
We think this book is a testament to the benefits and future possibilities of this kind of collaboration, the framework for which has been put in place by Richard Stallman and the Free Software Foundation.
In 1999, the College Board’s Advanced Placement (AP) Computer Science exam was given in C++ for the first time. As in many high schools throughout the country, the decision to change languages had a direct impact on the computer science curriculum at Yorktown High School in Arlington, Virginia, where I teach. Up to this point, Pascal was the language of instruction in both our first-year and AP courses. In keeping with past practice of giving students two years of exposure to the same language, we made the decision to switch to C++ in the first year course for the 1997-98 school year so that we would be in step with the College Board’s change for the AP course the following year.
Two years later, I was convinced that C++ was a poor choice to use for introducing students to computer science. While it is certainly a very powerful programming language, it is also an extremely difficult language to learn and teach. I found myself constantly fighting with C++’s difficult syntax and multiple ways of doing things, and I was losing many students unnecessarily as a result. Convinced there had to be a better language choice for our first-year class, I went looking for an alternative to C++.
I needed a language that would run on the machines in our GNU/Linux lab as well as on the Windows and Macintosh platforms most students have at home. I wanted it to be free software, so that students could use it at home regardless of their income. I wanted a language that was used by professional programmers, and one that had an active developer community around it. It had to support both procedural and object-oriented programming. And most importantly, it had to be easy to learn and teach. When I investigated the choices with these goals in mind, Python stood out as the best candidate for the job.
I asked one of Yorktown’s talented students, Matt Ahrens, to give Python a try. In two months he not only learned the language but wrote an application called pyTicket that enabled our staff to report technology problems via the Web. I knew that Matt could not have finished an application of that scale in so short a time in C++, and this accomplishment, combined with Matt’s positive assessment of Python, suggested that Python was the solution I was looking for.
Having decided to use Python in both of my introductory computer science classes the following year, the most pressing problem was the lack of an available textbook.
Free documents came to the rescue. Earlier in the year, Richard Stallman had introduced me to Allen Downey. Both of us had written to Richard expressing an interest in developing free educational materials. Allen had already written a first-year computer science textbook, How to Think Like a Computer Scientist. When I read this book, I knew immediately that I wanted to use it in my class. It was the clearest and most helpful computer science text I had seen. It emphasized the processes of thought involved in programming rather than the features of a particular language. Reading it immediately made me a better teacher.
How to Think Like a Computer Scientist was not just an excellent book, but it had been released under the GNU public license, which meant it could be used freely and modified to meet the needs of its user. Once I decided to use Python, it occurred to me that I could translate Allen’s original Java version of the book into the new language. While I would not have been able to write a textbook on my own, having Allen’s book to work from made it possible for me to do so, at the same time demonstrating that the cooperative development model used so well in software could also work for educational materials.
Working on this book for the last two years has been rewarding for both my students and me, and my students played a big part in the process. Since I could make instant changes whenever someone found a spelling error or difficult passage, I encouraged them to look for mistakes in the book by giving them a bonus point each time they made a suggestion that resulted in a change in the text. This had the double benefit of encouraging them to read the text more carefully and of getting the text thoroughly reviewed by its most important critics, students using it to learn computer science.
For the second half of the book on object-oriented programming, I knew that someone with more real programming experience than I had would be needed to do it right. The book sat in an unfinished state for the better part of a year until the open source community once again provided the needed means for its completion.
I received an email from Chris Meyers expressing interest in the book. Chris is a professional programmer who started teaching a programming course last year using Python at Lane Community College in Eugene, Oregon. The prospect of teaching the course had led Chris to the book, and he started helping out with it immediately. By the end of the school year he had created a companion project on our Website at http://openbookproject.net (Python for Fun)[http://openbookproject.net/py4fun>] and was working with some of my most advanced students as a master teacher, guiding them beyond where I could take them.
The process of translating and using How to Think Like a Computer Scientist for the past two years has confirmed Python’s suitability for teaching beginning students. Python greatly simplifies programming examples and makes important programming ideas easier to teach.
The first example from the text illustrates this point. It is the traditional hello, world program, which in the Java version of the book looks like this:
in the Python version it becomes:
Even though this is a trivial example, the advantages of Python stand out. Yorktown’s Computer Science I course has no prerequisites, so many of the students seeing this example are looking at their first program. Some of them are undoubtedly a little nervous, having heard that computer programming is difficult to learn. The Java version has always forced me to choose between two unsatisfying options: either to explain the class Hello
, public static void main
, String[] args
, {
, and }
, statements and risk confusing or intimidating some of the students right at the start, or to tell them, Just don’t worry about all of that stuff now; we will talk about it later, and risk the same thing. The educational objectives at this point in the course are to introduce students to the idea of a programming statement and to get them to write their first program, thereby introducing them to the programming environment. The Python program has exactly what is needed to do these things, and nothing more.
Comparing the explanatory text of the program in each version of the book further illustrates what this means to the beginning student. There are seven paragraphs of explanation of Hello, world! in the Java version; in the Python version, there are only a few sentences. More importantly, the missing six paragraphs do not deal with the big ideas in computer programming but with the minutia of Java syntax. I found this same thing happening throughout the book. Whole paragraphs simply disappear from the Python version of the text because Python’s much clearer syntax renders them unnecessary.
Using a very high-level language like Python allows a teacher to postpone talking about low-level details of the machine until students have the background that they need to better make sense of the details. It thus creates the ability to put first things first pedagogically. One of the best examples of this is the way in which Python handles variables. In Java a variable is a name for a place that holds a value if it is a built-in type, and a reference to an object if it is not. Explaining this distinction requires a discussion of how the computer stores data. Thus, the idea of a variable is bound up with the hardware of the machine. The powerful and fundamental concept of a variable is already difficult enough for beginning students (in both computer science and algebra). Bytes and addresses do not help the matter. In Python a variable is a name that refers to a thing. This is a far more intuitive concept for beginning students and is much closer to the meaning of variable that they learned in their math courses. I had much less difficulty teaching variables this year than I did in the past, and I spent less time helping students with problems using them.
Another example of how Python aids in the teaching and learning of programming is in its syntax for functions. My students have always had a great deal of difficulty understanding functions. The main problem centers around the difference between a function definition and a function call, and the related distinction between a parameter and an argument. Python comes to the rescue with syntax that is nothing short of beautiful. Function definitions begin with the keyword def
, so I simply tell my students, When you define a function, begin with def
, followed by the name of the function that you are defining; when you call a function, simply call (type) out its name. Parameters go with definitions; arguments go with calls. There are no return types, parameter types, or reference and value parameters to get in the way, so I am now able to teach functions in less than half the time that it previously took me, with better comprehension.
Using Python improved the effectiveness of our computer science program for all students. I saw a higher general level of success and a lower level of frustration than I experienced teaching with either C++ or Java. I moved faster with better results. More students left the course with the ability to create meaningful programs and with the positive attitude toward the experience of programming that this engenders.
I have received email from all over the globe from people using this book to learn or to teach programming. A user community has begun to emerge, and many people have been contributing to the project by sending in materials for the companion Website at http://openbookproject.net/pybiblio.
With the continued growth of Python, I expect the growth in the user community to continue and accelerate. The emergence of this user community and the possibility it suggests for similar collaboration among educators have been the most exciting parts of working on this project for me. By working together, we can increase the quality of materials available for our use and save valuable time. I invite you to join our community and look forward to hearing from you. Please write to me at jeff@elkner.net.
Jeffrey Elkner
Governor’s Career and Technical Academy in Arlington
Arlington, Virginia
By Peter Wentworth
A colleague and friend, Peter Warren, once made the remark that learning introductory programming is as much about the environment as it is about the programming language.
I’m a big fan of IDEs (Integrated Development Environments).
I want help to be integrated into my editor, as a first-class citizen, available at the press of a button. I want syntax highlighting. I want immediate syntax checking, and sensible autocompletion. I’d like an editor that can fold function bodies or regions of code away, because it promotes and encourages how we build mental abstractions.
I’m especially keen on having a single-stepping debugger and breakpoints with code inspection built in. We’re trying to build a conceptual model of program execution in the student’s mind, so I find most helpful for teaching to have the call stack and variables explicitly visible, and to be able to immediately inspect the result of executing a statement.
My philosophy, then, is not to look for a language to teach, but to look for a combination of IDE and language that are packaged together, and evaluated as a whole.
I’ve made some quite deep changes to the original book to reflect this (and various other opinionated views that I hold), and I have no doubt that more changes will follow as we mature our course.
Here are some of the key things I’ve approached differently:
tess.forward(100)
. Similarly, when we use random numbers, we avoid the “hidden singleton generator” in the random module — we prefer to create an instance of a generator, and invoke methods on the instance.for
loop seem to be winners in Python, so rather than use the traditional command-line input
for data, I’ve favoured using loops and lists right up front, like this:friends = ["Zoe", "Joe", "Bill"]
for f in friends:
invitation = "Hi " + f + ". Please come to my party on Saturday!"
print(invitation)
This also means that I bumped range
up for early exposure.
I envisage that over time we’ll see more opportunities to exploit “early lists, early iteration” in its most simple form.
doctest
: it is too quirky for my liking. For example, it fails a test if the spacing between list elements is not precisely the same as the output string, or if Python prints a string with single quotes, but you wrote up the test case with double quotes. Cases like this also confused students (and instructors) quite badly:If you can explain the difference in scope rules and lifetimes between the parameter xs
and the doctest variable xs
elegantly, please let me know. Yes, I know doctest creates its own scope behind our back, but it is exactly this black magic that we’re trying to avoid.
From the usual indentation rules, also looks like the doctests are nested inside the function scope, but they are not. Students thought that the parameter had been given its value by the assignment to xs
in the doctest!
I also think that keeping the test suite separate from the functions under test leads to a cleaner relationship between caller and callee, and gives a better chance of getting argument passing / parameter concepts taught accurately.
There is a good unit testing module in Python, (and PyScripter offers integrated support for it, and automated generation of skeleton test modules), but it looked too advanced for beginners, because it requires multi-module concepts.
So I’ve favoured my own test scaffolding in Chapter 6 (about 10 lines of code) that the students must insert into whatever file they’re working on.
I’ve played down command-line input / process / output where possible. Many of our students have never seen a command-line shell, and it is arguably quite intimidating.
We’ve gone back to a more “classic / static” approach to writing our own classes and objects. Python (in company with languages like Javascript, Ruby, Perl, PHP, etc.) don’t really emphasize notions of “sealed” classes or “private” members, or even “sealed instances”.
So one teaching approach is to allocate each instance as an empty container, and subsequently allow the external clients of the class to poke new members (methods or attributes) into different instances as they wish to.
It is a very dynamic approach, but perhaps not one that encourages thinking in abstractions, layers, contracts, decoupling, etc. It might even be the kind of thing that one could write one of those “x,y,z … considered harmful” papers about.
In our more conservative approach, we put an initializer into every class, we determine at object instantiation time what members we want, and we initialize the instances from within the class. So we’ve moved closer in philosophy to C# / Java on this one.
We’re moving towards introducing more algorithms earlier into the course. Python is an efficient teaching language — we can make fast progress. But the gains we make there we’d like to invest into deeper problem solving, and more complex algorithms with the basics, rather than cover “more Python features”. Some of these changes have started to find their way in this version, and I’m sure we’ll see more in future.
We’re interested in issues around teaching and learning. Some research indicates that “intellectual playfulness” is important. The study referenced in the Odds-and-ends workbook at the end just didn’t seem to have anywhere sensible to go in the book, yet I wanted it included. It is quite likely that we’ll allow more issues like this to creep into the book, to try to make it more than just about programming in Python.
Version 1.3, 3 November 2008
Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. https://fsf.org/
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
The purpose of this License is to make a manual, textbook, or other functional and useful document “free” in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.
This License is a kind of “copyleft”, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The “Document”, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you”. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A “Modified Version” of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The “Invariant Sections” are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.
The “Cover Texts” are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A “Transparent” copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not “Transparent” is called “Opaque”.
Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text.
The “publisher” means any person or entity that distributes copies of the Document to the public.
A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as “Acknowledgements”, “Dedications”, “Endorsements”, or “History”.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version’s license notice. These titles must be distinct from any other section titles.
You may add a section Entitled “Endorsements”, provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled “History” in the various original documents, forming one section Entitled “History”; likewise combine any sections Entitled “Acknowledgements”, and any sections Entitled “Dedications”. You must delete all sections Entitled “Endorsements”.
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled “Acknowledgements”, “Dedications”, or “History”, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/licenses/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License “or any later version” applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Document.
“Massive Multiauthor Collaboration Site” (or “MMC Site”) means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A “Massive Multiauthor Collaboration” (or “MMC”) contained in the site means any set of copyrightable works thus published on the MMC site.
“CC-BY-SA” means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.
“Incorporate” means to publish or republish a Document, in whole or in part, as part of another Document.
An MMC is “eligible for relicensing” if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.
The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.
To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
Copyright (C) YEAR YOUR NAME.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled "GNU
Free Documentation License".
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the “with … Texts.” line with this:
with the Invariant Sections being LIST THEIR TITLES, with the
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.
If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
Comments
As programs get bigger and more complicated, they get more difficult to read. Formal languages are dense, and it is often difficult to look at a piece of code and figure out what it is doing, or why.
For this reason, it is a good idea to add notes to your programs to explain in natural language what the program is doing.
A comment in a computer program is text that is intended only for the human reader — it is completely ignored by the interpreter.
In Javascript, the
\\
token starts a comment. The rest of the line is ignored. Here is a new version of Hello, World!.You’ll also notice that we’ve left a blank line in the program. Blank lines are also ignored by the interpreter, but comments and blank lines can make your programs much easier for humans to parse. Use them liberally!
Javascript also supports multiline comments with the
/* */
style.In addition to adding hints and suggestions for human readers, comments play an important role in debugging. Because the Javascript interpreter doesn’t try to run commented lines, you can “comment out” sections of your code to isolate errors.
In the above code, only line 1 is interpreted and run as Javascript. The other lines are ignored. To debug this program, we can uncomment one line at a time until we find out which line has the buggy code.
It is so common for programmers to comment out large blocks of code when they are testing their programs, that programmer’s text editors support quickly commenting out sections of code. In repl.it, our online editor for this book, you can simply highlight the lines you want to comment or uncomment and use the Ctrl + / keyboard shortcut. You will see us using this technique in our example videos.