Chapter 4. Putting some order in your Data: Arrays
There’s more to JavaScript than numbers, strings, and booleans. So far you’ve been writing JavaScript code with primitives—strings, numbers, and booleans, like “Fido”, 23, and true. You can do a lot with primitive types, but at some point you’ve got to deal with more data. Say, all the items in a shopping cart, or all the songs in a playlist, or a set of stars and their apparent magnitude, or an entire product catalog. For that you need a little more oomph. The type of choice for this kind of ordered data is a JavaScript array, and in this chapter we’re going to walk through how to put your data into an array, how to pass it around, and how to operate on it. We’ll be looking at a few other ways to structure your data in later chapters, but let’s get started with arrays.
Can you help Bubbles-R-Us?
Meet the Bubbles-R-Us company. Their tireless research makes sure bubble wands and machines everywhere blow the best bubbles. Today, they’re testing the “bubble factor” of several variants of their new bubble solution; that is, they’re testing how many bubbles a given solution can make. Here’s their data:
Of course, you want to get all this data into JavaScript so you can write code to help analyze it. But that’s a lot of values. How are you going to construct your code to handle all these values?
How to represent multiple values in JavaScript
You know how to represent single values like strings, numbers, and booleans with JavaScript, but how do we represent multiple values, like all the bubble factor scores from the ten bubble solutions? To do that, we use JavaScript arrays. An array is a JavaScript type that can hold many values. Here’s a JavaScript array that holds all the bubble factor scores:
You can treat all the values as a whole, or you can access the individual scores when you need to. Check this out:
How arrays work
Before we get on to helping Bubbles-R-Us, let’s make sure we’ve got arrays down. As we said, you can use arrays to store multiple values (unlike variables that hold just one value, like a number or a string). Most often you’ll use arrays when you want to group together similar things, like bubble factor scores, ice cream flavors, daytime temperatures, or even the answers to a set of true/false questions. Once you have a bunch of values you want to group together, you can create an array that holds them, and then access those values in the array whenever you need them.
How to create an array
Let’s say you wanted to create an array that holds ice cream flavors. Here’s how you’d do that:
When you create an array, each item is placed at a location, or index, in the array. With the flavors array, the first item, “vanilla”, is at index 0, the second, “butterscotch”, is at index 1, and so on. Here’s a way to think about an array:
How to access an array item
Each item in the array has its own index, and that’s your key to both accessing and changing the values in an array. To access an item, just follow the array variable name with an index, surrounded by square brackets. You can use that notation anywhere you’d use a variable:
Updating a value in the array
You can also use the array index to change a value in an array. So, after this line of code, your array will look like this:
How big is that array anyway?
Say someone hands you a nice big array with important data in it. You know what’s in it, but you probably won’t know exactly how big it is. Luckily, every array comes with its own property, length
. We’ll talk more about properties and how they work in the next chapter, but for now, a property is just a value associated with an array. Here’s how you use the length
property:
<!doctype html> <html lang="en"> <head> <title>Phrase-O-Matic</title> <meta charset="utf-8"> <script> function makePhrases() { let words1 = ["24/7", "multi-tier", "30,000 foot", "B-to-B", "win-win"]; let words2 = ["empowered", "value-added", "oriented", "focused", "aligned"]; let words3 = ["process", "solution", "tipping point", "strategy", "vision"]; let rand1 = Math.floor(Math.random() * words1.length); let rand2 = Math.floor(Math.random() * words2.length); let rand3 = Math.floor(Math.random() * words3.length); let phrase = words1[rand1] + " " + words2[rand2] + " " + words3[rand3]; alert(phrase); } makePhrases(); </script> </head> <body></body> </html>
Note
Check out this code for the hot new Phrase-O-Matic app and see if you can figure out what it does before you go on...
You didn’t think our serious business application from Chapter 1 was serious enough? Fine. Try this one, if you need something to show the boss.
The Phrase-O-Matic
We hope you figured out this code is the perfect tool for creating your next start-up marketing slogan. It has created winners like “B-to-B value-added strategy” and “24/7 empowered process” in the past, and we have high hopes for more winners in the future. Let’s see how this thing really works:
First, we define the
makePhrases
function, which we can call as many times as we want to generate the phrases we want.With that out of the way, we can write the code for the
makePhrases
function. Let’s start by setting up three arrays. Each will hold words that we’ll use to create the phrases. In the next step, we’ll pick one word at random from each array to make a three-word phrase.Now we generate three random numbers, one for each of the three random words we want to pick to make a phrase. Remember from Chapter 2 that Math.random generates a number between 0 and 1 (not including 1). If we multiply that by the length of the array and use Math.floor to truncate the number, we get a number between 0 and one less than the length of the array.
Now we create the slick marketing phrase by taking each randomly chosen word and concatenating them all together, with nice spaces in between for readability.
We’re almost done; we have the phrase, now we just have to display it. We’re going to use
alert
(but you could also useconsole.log
and check your browser’s console window).alert(phrase);
Okay, finish that last line of code, have one more look over it all, and feel that sense of accomplishment before you load it into your browser. Give it a test drive and enjoy the phrases.
Meanwhile, back at Bubbles-R-Us...
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
Let’s take a closer look at what the CEO is looking for:
Cubicle Conversation
Mara: The first thing we need to do is display every score along with its solution number.
Alex: And the solution number is just the index of the score in the array, right?
Mara: Oh, yeah, that’s totally right.
Sam: Slow down a sec. So we need to take each score, print its index, which is the bubble solution number, and then print the corresponding score.
Mara: You’ve got it, and the score is just the corresponding value in the array.
Alex: So, for bubble solution #10, its score is just scores[10]
.
Mara: Right.
Sam: Okay, but there are a lot of scores. How do we write code to output all of them?
Mara: Iteration, my friend.
Sam: Oh, you mean like a while loop?
Mara: Right, we loop through all the values from zero to the length...oh, I mean the length minus one, of course.
Sam: This is starting to sound very doable. Let’s write some code; I think we know what we’re doing.
Mara: That works for me! Let’s do it, and then we’ll come back to the rest of the report.
How to iterate over an array
Your goal is to produce some output that looks like this:
We’ll do that by outputting the score at index zero, and then we’ll do the same for indices one, two, three and so on, until we reach the last index in the array. You already know how to use a while loop. Let’s see how we can use that to output all the scores:
Note
And then we’ll show you a better way in a sec...
But wait, there’s a better way to iterate over an array
We should really apologize. We can’t believe it’s already Chapter 4 and we haven’t even introduced you to the for loop. Think of the for loop as the while loop’s cousin. The two basically do the same thing, except the for loop is usually a little more convenient to use. Check out the while loop we just used, and we’ll see how that maps into a for loop.
Now let’s look at how the for loop makes all that so much easier:
Good catch, and great question.
Yes, a for loop statement creates a block just like a while loop statement does. Every statement between the curly braces is in the for loop block:
The for loop statement is interesting because you can also think of the three parts of the statement between the parentheses as being part of the block:
That means the declaration of the i
loop variable is local to the block, meaning the value of i
will not be visible outside the block. So you would not be able to do this, for instance:
for (let i = 0; i < scores.length; i = i + 1) { let output = "Bubble solution #" + i + " score: " + scores[i]; console.log(output); } console.log("There are " + i + " bubble solutions in scores");
Note
If you try this at home, you’ll see an error in the console: Uncaught ReferenceError: i is not defined.
Test drive the bubble report
Save this file as “bubbles.html” and load it into your browser. Make sure you’ve got the console visible (you might need to reload the page if you activate the console after you load the page), and check out the brilliant report you just generated for the Bubbles-R-Us CEO.
It’s that time again... can we talk about your verbosity?
You’ve been writing lots of code that looks like this:
In fact, this kind of statement is so common there’s a shortcut for it in JavaScript. It’s called the post-increment operator, and despite its fancy name, it’s quite simple. Using the post-increment operator, we can replace the above line of code with this:
Of course, it just wouldn’t feel right if there wasn’t a post-decrement operator as well. You can use the post-decrement operator on a variable to reduce its value by one, like this:
And why are we telling you this now? Because these are commonly used with for
statements. Let’s clean up our code a little using the post-increment operator...
Redoing the for loop with the post-increment operator
Let’s do a quick rewrite and test to make sure the code works the same as before:
let scores = [60, 50, 60, 58, 54, 54,
58, 50, 52, 54, 48, 69,
34, 55, 51, 52, 44, 51,
69, 64, 66, 55, 52, 61,
46, 31, 57, 52, 44, 18,
41, 53, 55, 61, 51, 44];
for (let i = 0; i < scores.length; i++) {
let output = "Bubble solution #" + i +
" score: " + scores[i];
console.log(output);
}
Note
All we’ve done is update where we increment the loop variable with the post-increment operator.
Another quick test drive
Time to do a quick test drive to make sure the change to use the post-increment operator works. Save your file, “bubbles.html”, and reload. You should see the same report you saw before.
Cubicle Conversation continued...
Mara: Right, and the first thing we need to do is determine the total number of bubble tests. That’s easy; it’s just the length of the scores array.
Alex: Oh, right. We’ve got to find the highest score too, and then the solutions that have the highest score.
Mara: Yeah, that last one is going to be the toughest. Let’s work out finding the highest score first.
Alex: Sounds like a good place to start.
Mara: To do that, I think we just need to maintain a highest score variable that keeps track as we iterate through the array. Here, let me write some pseudocode:
Alex: Oh nice; you did it with just a few lines added to our existing code.
Mara: Yup. Each time through the array we look to see if the current score is greater than highScore
, and if so, that’s our new high score. Then, after the loop ends we just display the high score.
“More than one”...hmmm. When we need to store more than one thing, what do we use? An array, of course. So can we iterate through the scores array looking for only scores that match the highest score, and then add them to an array that we can later display in the report? You bet we can, but to do that we’ll have to learn how to create a brand new empty array, and then figure out how to add new elements to it.
Creating an array from scratch (and adding to it)
Before we take on finishing this code, let’s get a sense of how to create a new array and add new items to it. You already know how to create an array with values, like this:
But you can also omit the initial items and just create an empty array:
And you already know how to add new values to an array. To do that you just assign a value to an item at an index, like this:
When adding new items this way, you have to be careful about which index you’re adding. Otherwise, you might create a sparse array, which is an array with “holes” in it (like an array with values at 0 and 2, but no value at 1). Having a sparse array isn’t necessarily a bad thing, but it does require special attention. There’s another way to add new items without worrying about the index, and that’s push
. Here’s how it works:
Mara: Yes, we’ll start with an empty array to hold the solutions with the highest score and add each solution that has that high score to it one at a time as we iterate through the scores array.
Sam: Great, let’s get started.
Mara: But hold on a second...I think we might need a separate loop.
Sam: We do? Seems like there should be a way to do it in our existing loop.
Mara: Yup, I’m sure we do. Here’s why: we have to know what the highest score is before we can find all the solutions that have that highest score. So we need two loops: one to find the highest score, which we’ve already written, and then a second one to find all the solutions that have that score.
Sam: Oh, I see. And in the second loop, we’ll compare each score to the highest score, and if it matches, we’ll add the index of the bubble solution score to the new array we’re creating for the solutions with the highest score.
Mara: Exactly! Let’s do it.
Test drive the final report
Go ahead and add the code to identify the bubble solutions with the highest score to your code in “bubbles.html”, then run another test drive. All the JavaScript code is shown below:
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44]; let highScore = 0; for (let i = 0; i < scores.length; i++) { let output = "Bubble solution #" + i + " score: " + scores[i]; console.log(output); if (scores[i] > highScore) { highScore = scores[i]; } } console.log("Bubbles tests: " + scores.length); console.log("Highest bubble score: " + highScore); let bestSolutions = []; for (let i = 0; i < scores.length; i++) { if (scores[i] == highScore) { bestSolutions.push(i); } } console.log("Solutions with the highest score: " + bestSolutions);
And the winners are...
Bubble solutions #11 and #18 both have a high score of 69! So they are the best bubble solutions in this batch of test solutions.
You’re right, we should be. Given that you just learned about functions, we wanted to get the basics of arrays out of the way before employing them. That said, you always want to think about which parts of your code you can abstract away into a function. Not only that, but say you wanted to reuse, or let others reuse, all the work that went into writing the bubble computations—you’d want to give other developers a nice set of functions they could work with.
Let’s go back to the bubble scores code and refactor it into a set of functions. By refactor, we mean we’re going to rework how it’s organized to make it more readable and maintainable, but we’re going to do that without altering what the code does. In other words, when we’re done, the code will do exactly what it does now, but it’ll be a lot better organized.
Writing the printAndGetHighScore function
We’ve got the code for the printAndGetHighScore
function already. It’s just the code we’ve already written, but to make it a function, we need to think through what arguments we’re passing it and if it returns anything back to us.
Passing in the scores array seems like a good idea, because that way we can reuse the function on other arrays of bubble scores. And we want to return the high score that we compute in the function, so the code that calls the function can do interesting things with it (and, after all, we’re going to need it to figure out the best solutions).
Oh, and another thing: often you want your functions to do one thing well. Here, we’re doing two things: we’re displaying all the scores in the array, and we’re also computing the high score. We might want to consider breaking this into two functions, but given how simple things are right now we’re going to resist the temptation. If we were working in a professional environment we might reconsider and break this into two functions, printScores
and getHighScore
. For now, though, we’ll stick with one function. Let’s get this code refactored:
Refactoring the code using printAndGetHighScore
Now, we need to change the rest of the code to use our new function. To do so, we simply call the new function and set the variable highScore
to the result of the printAndGetHighScore
function:
Putting it all together
Once you’ve completed refactoring your code, make all the changes to “bubbles.html”, just like we have below, and reload the bubble report. You should get exactly the same results as before, but now you know your code is more organized and reusable. Create your own scores array and try some reuse!
So, what’s the job here? It’s to take the leading bubble solutions—that is, the ones with the highest bubble scores—and choose the lowest-cost one. Now, luckily, we’ve been given a costs
array that mirrors the scores
array. That is, the bubble solution score at index 0 in the scores
array has the cost at index 0 in the costs
array (.25), the bubble solution at index 1 in the scores
array has the cost at index 1 in the costs
array (.27), and so on. So, for any score you’ll find its cost in the costs
array at the same index. Sometimes we call these parallel arrays:
Mara: Well, we know the highest score already.
Sam: Right, but how do we use that? And we have these two arrays; how do we get those to work together?
Mara: I’m pretty sure either of us could write a simple for loop that goes through the scores
array again and picks up the items that match the highest score.
Sam: Yeah, I could do that. But then what?
Mara: Anytime we hit a score that matches the highest score, we need to see if its cost is the lowest we’ve seen.
Sam: Oh I see, so we’ll have a variable that keeps track of the index of the lowest-cost high score. Wow, that’s a mouthful.
Mara: Exactly. And once we get through the entire array, whatever index is in that variable is the index of the item that not only matches the highest score, but has the lowest cost.
Sam: What if two items match in cost?
Mara: Hmm, we have to decide how to handle that. I’d say, whatever one we see first is the winner. Of course, we could do something more complex, but let’s stick with that unless the CEO says differently.
Sam: This is complicated enough that I think I want to sketch out some pseudocode before writing anything.
Mara: I agree; whenever you are managing indices of multiple arrays things can get tricky. Let’s do that. In the long run I’m sure it will be faster to plan it first.
Sam: Okay, I’ll take a first stab at it...
Get Head First JavaScript Programming, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.