Enough to write a small program
A function of your own, a decision, a loop, a list to collect an answer in, and the handful of text operations the problem sets need.
Everything so far has been about single lines and single values: what they mean, what they are, and when they exist. The problem sets at the end of this section ask for something slightly larger, a small program that takes something in, does a little work, and hands an answer back.
That needs five constructions this section has not taught, because each of them has a section of its own later. What follows is the minimum of each: enough to write the problem sets, and nowhere near the whole story. Nothing here is a summary you should trust in place of the full treatment later.
A function of your own
You have been calling functions since the second lesson: console.log, String, Math.max. Writing one is how you give a piece of work a name, so that it can be done again with different values.
Change n * 2 to n * 10 and predict both numbers before you run it again.
Four parts, reading left to right. The word function says a definition follows. double is the name. (n) is the parameter list: names that will hold whatever the caller supplies. The block in braces is the body, which is the work.
A parameter exists only inside the body. When the call double(5) runs, n is 5 for the length of that call and is gone afterwards. Calling it again with 3 starts over with n as 3.
return does two things at once: it hands a value back to whoever called, and it ends the function immediately. Nothing after a return that has run will happen. The second line above shows the value coming back, because double(3) became 6 before the outer call ever saw it.
Swap the two arguments in the first call and run it again. Order is the only thing that matches an argument to a parameter.
+ between two pieces of text joins them end to end, with nothing added in between, which is why the comma and space are written out. Every problem set in this section is exactly this shape: a function with a given name and given parameters, and a body for you to write.
Choosing: if
An if runs a block only when something is true. What goes in its brackets is an expression that produces true or false, and the operators that produce those are worth listing once.
| Written | True when |
|---|---|
a === b | Same type and same value. The one to use. |
a !== b | Not the same type or not the same value. |
a > b | a is greater. |
a < b | a is less. |
a >= b | a is greater or they are equal. |
a <= b | a is less or they are equal. |
Change the 100 to 300 and predict both numbers before running it.
With postage(250), the comparison is true, the block runs, and return 5 ends the function there and then. With postage(30) it is false, the block is skipped entirely, and the next line down runs instead.
That pattern, an if whose block returns, needs no else at all: reaching the line below the if already means the condition was false. When you do want to name both branches, else does it.
Add a branch for count === 2 that returns "a pair", and check it before the else.
The branches are tried from the top, and the first one whose condition is true is the only one that runs. else on its own is what happens when none of them was.
A list of values
An array is an ordered list of values, written as square brackets with commas between the items. It is an object, so it is the eighth type from the types lesson, and it has a section of its own later.
Change prices[9] to prices[1]. Reading a position that does not exist is not an error; it is undefined.
Positions are counted from zero, so the first item is prices[0] and the last of three is prices[2]. prices.length is how many there are, which is always one more than the last position. That difference is the source of more early mistakes than anything else in this lesson.
Repeating: for ... of
A loop runs a block more than once. The for ... of form runs it once for each item of an array, handing you the item itself.
Add console.log("so far:", total); as the last line inside the block, and watch the sum being built.
Read it as "for each price of prices". On the first pass price is 3, on the second 5, on the third 2, and then the loop is finished.
price is declared with const even though it holds three different values, because each pass is a fresh block with a fresh name in it. total has to be let, because that one name is reassigned on every pass, and a name that is going to move cannot be const.
Two words change the flow inside a loop. continue abandons this pass and starts the next one. break stops the loop altogether.
Change continue; to break; and run it again. One skips the empty word, the other gives up at it.
Counting, when you need the position
for ... of gives you the items and not their positions. When the position matters, and it does in one of the problem sets, there is an older form that counts.
Change the 1 to 0 and run it again. That one character decides whether the first line is visited.
The brackets hold three parts separated by semicolons: where to start, the condition to keep going, and what to do after each pass. let i = 1 starts at the second item, i < lines.length stops after the last one, and i++ adds one to i.
i < lines.length rather than i <= lines.length is the whole reason positions count from zero: with three items the positions are 0, 1 and 2, and length is 3, so the loop must stop before it.
Collecting an answer
Most of these problems build an answer a piece at a time. The reliable way is an empty array, one push per piece, and a join at the end.
Delete the two push lines with numbers in them and run it again. join still gives a sensible answer with one part, and with none.
parts.push(value) adds one item to the end. parts.join(separator) glues every item together with that separator between each pair and nothing at either end.
That last property is worth dwelling on, because it does the awkward work for you. With three parts, join(" ") puts in two spaces. With one part it puts in none. With an empty array it gives the empty string. Adding a separator yourself and trimming the extra one off the end gets all three of those cases wrong at least once.
const parts = [] and then pushing to it is not a contradiction: const fixes which array the name refers to, and pushing changes what is inside it, which is the lesson on let and const in one line.
Text, in pieces
Text has a section of its own later. These are the operations the problem sets in this section need, and no more.
| Written | Gives back |
|---|---|
text.length | How many characters there are. |
text[0] | The first character, as a one-character piece of text. |
text.trim() | The same text with spaces removed from both ends. |
text.trimEnd() | The same, but from the end only. |
text.split(",") | An array of the pieces between each comma. |
text.slice(2, 5) | The characters from position 2 up to, and not including, 5. |
text.indexOf("=") | The position of the first =, or -1 if there is none. |
text.startsWith("#") | true or false. |
text.includes("//") | true or false. |
Change "port=8080" to "greeting=hello=there" and look at the last two lines of output. indexOf finds the first = only.
at is 4, because p, o, r and t sit at positions 0 to 3. slice(0, at) takes everything before position 4, giving port. slice(at + 1) starts one past the = and runs to the end, giving 8080. Splitting on a character that appears twice in a row leaves an empty piece between them, which is what the empty string in the last line is.
One worked example, every step
Here is a small job of the kind the problem sets ask for. Somebody has typed a list of words separated by commas, sloppily, with stray spaces and a couple of empty entries. Give back one tidy line.
Change the ", " in the join to " and " and run it again. Then try an input that is nothing but commas.
text = "apple , , pear,banana "
text.split(",") -> ["apple ", " ", " pear", "banana "]
pass 1 raw = "apple " word = "apple" kept parts = ["apple"]
pass 2 raw = " " word = "" skipped parts = ["apple"]
pass 3 raw = " pear" word = "pear" kept parts = ["apple", "pear"]
pass 4 raw = "banana " word = "banana" kept parts = ["apple", "pear", "banana"]
parts.join(", ") -> "apple, pear, banana"Look at what the two awkward inputs did without any special handling. tidyList("") splits into one empty piece, which is trimmed to the empty string and skipped, so parts stays empty and join gives the empty string back. tidyList(" solo ") collects one word, and join puts no separator in because there is no pair to put one between.
Working on a problem set
- Read the worked example in the statement before the rules. It is the same information in the form you can check yourself against.
- Write the shape first: the loop, the
if, and areturnof something plainly wrong. Run it. A wrong answer from a program that runs is progress; a program that will not run is not. - Print what you have part way through, with
console.log, and delete the printing when it works. - Take the awkward cases seriously: empty input, one item, and the ends. They are what the hidden tests are made of, and the worked examples in each statement point at them on purpose.
Why does parts.join(" ") need no special case for an array with one item in it, or none?
Because join puts its separator between each pair of items, not after each item. One item has no pair, so no space is added and you get the item on its own. No items has no pair either, so you get the empty string. The version that goes wrong is adding word + " " on every pass, which leaves a space on the end that then has to be trimmed off, and that trimming is a second chance to get it wrong.