Staying in the Loop - Ruby Wizardry: An Introduction to Programming for Kids (2014)

Ruby Wizardry: An Introduction to Programming for Kids (2014)

Chapter 4. Staying in the Loop

Ruby on Monorails

“Well,” said the King, “all this adventuring’s gotten me as hungry as a lumberjack. And I haven’t eaten anything since my breakfast of parched oats!”

“It’s about lunchtime,” said Ruben. “What’s there to eat?”

“Nothing here,” said the King gloomily. “I’m afraid I pretty much wrecked the Royal Kitchen and Royal Pantry when I turned the palace upside-down looking for my string, and I don’t think the cooks have quite gotten everything back in order yet.”

“We can go out!” said Scarlet. “I’m sure there are good places to eat in the kingdom outside the palace walls.”

The King nodded vigorously. “Of course!” he said. “We’ll take the Loop to the Hashery. It’s my favorite restaurant!”

“What’s the Loop?” asked Scarlet.

“I’m glad you asked,” said the King, who was busy pulling on his finest traveling cloak and overbritches. “The Loop is the monorail—a sort of train—that runs throughout the kingdom, taking my subjects anywhere they’d like to go. It’s only a few stops to the Hashery from here!”

“Couldn’t we take a royal carriage or something?” asked Ruben.

“Where’s the fun in that?” replied the King. “Now hurry up—the next Loop train should be arriving outside the palace in just a few minutes.”

The King, Scarlet, and Ruben left the King’s study and traveled through corridor after corridor of the palace, stepping over and around cooks, maids, butlers, handymen, and a host of other palace employees who were busy righting all the things the King had flipped upside-down in his mad search for his string. Finally, they arrived at the great wooden gate of the palace, and a pair of very strong-looking attendants saluted smartly and pulled the doors open for the trio.

“Where’s the Loop stop?” Ruben asked, blinking in the sudden sunshine.

“Just over there,” said the King, and pointed to a large metal platform at the top of a small hill near the palace entrance. “See that rail? The Loop train runs on that. It’ll come up to the platform in a few minutes, then head out toward the east side of the kingdom.”

“It’s so high up!” said Scarlet. “Is it safe?”

“Absolutely!” said the King. “You’ll see.”

After a few minutes of walking, the King, Scarlet, and Ruben arrived at the platform. Just as Ruben was about to ask how long the train would take to get there, a bright red metal train car whizzed up to the platform, and the door opened with a gentle whoosh. “Aha! Here we are,” said the King. “All aboard!”

The doors closed quickly behind them, and with barely a sound, the Loop train sped away from the palace station. Ruben looked around. “There’s no one here!” he said. “A car to ourselves!” He spread out on a plastic bench along one side of the train car.

“No one at all,” said Scarlet. “Not even a conductor. How’s that possible?”

“No need for a conductor!” said the King. “The Loop is fully automatic. It runs entirely on Ruby!”

“Rails running on Ruby?” said Ruben. “Awesome!”

“I’m not so sure,” said Scarlet. “We saw how well the Flowmatic Something-or-Other worked without someone keeping an eye on it.”

“Oh, I don’t think there’s anything to worry about,” said the King. “The Loop has run for years without any sort of problem.”

Ruben pressed his nose to the glass. “We’ll be there in no time!” he said. “It looks like the Loop is heading nonstop to the Hashery.”

“What do you mean?” said the King.

“We’ve passed two other platforms without stopping—this is great! Well, maybe not so great for the people on those other platforms, but, you know, more Hashery for us.”

The King’s eyes went wide. “The Loop should stop at every station if there are people waiting!” he said. “Something must be wrong if we’re skipping any.”

“Nothing to worry about, huh?” said Scarlet. “We’re stuck on an out-of-control train!”

“Awesome!” said Ruben.

image with no caption

“Now, now,” said the King. “If this morning’s been any indication, I’m sure there’s a Computing Contraption around here somewhere that we can pry into to get an idea of what’s going on.” All three quickly scanned the train, looking for hidden compartments or mysterious devices. It wasn’t long before Ruben spotted a square of metallic mesh with a small red button beside it. When he pressed the button, the mesh grid slid up with a slight squeak, revealing the cheerful glow of an IRB >> prompt.

“Found it!” said Ruben, waving Scarlet and the King over.

“Great!” said Scarlet. “Let’s see if we can figure out a way to stop this thing.”

“Make it quick!” said the King. “We don’t want to miss our stop. The Hashery serves breakfast all day, but if you get there late, sometimes they run out of the best dishes. Like hash!”

Scarlet was busy inspecting the Ruby code on the Computing Contraption’s screen. “Oh no!” she said. “It looks like we’re caught in an infinite loop!”

“Sweet breakfast gravy!” cried the King. “What’s that?”

while Loops

“An infinite loop is a Ruby instruction that never ends,” said Ruben. “In Ruby, a loop is a bit of code that runs repeatedly, doing whatever its instructions tell it to until it’s supposed to stop. But if you give it a stopping condition that never happens, the code runs forever!”

“Take a look,” said Scarlet. “It looks like the code that drives the train will never stop running!” When the King squinted at the screen, this is what he saw.

NOTE

Just read these next few examples—don’t try them out in IRB. These little bits of code (shown in gray) would only work as part of a longer program.

while true

drive_train_forward

end

“I think I’ve heard of this,” said the King. “This loop is a while loop, a bit of code that repeats while some condition is true. But since this loop starts with while true, and true is always true, the loop will call the drive_train_forward method forever!”

“Exactly,” said Scarlet. “We need a way to tell the loop to stop.”

“What about this?” Ruben said, pointing to a yellowed piece of paper tucked next to the Computing Contraption’s screen. The King bent forward to read it. “‘A Very Brief Guide to the Loop and Its Machinations,’” he quoted. “This looks promising!”

image with no caption

“It says here that there’s not only a drive_train_forward method but also a stop_train method, which should stop the train for us,” Ruben said. “Try using that!”

“Sure thing!” said Scarlet. She quickly changed the code in the Computing Contraption to:

while true

stop_train

end

As soon as she pressed ENTER, the train made a deep, sad boooooop that faded away in just under a second, and as the sound trailed off, the train began to slow. Before they knew it, their train car was standing perfectly still on the monorail track.

“Nice work!” said Ruben.

“Well, you did stop the train,” said the King. “But take a look out that window.” Ruben and Scarlet ran to where the King pointed and looked out the window at the front of the car. Their hearts sank. “We’re stuck between platforms!” said Scarlet. “I can’t even see the next one on the track ahead.”

“Let’s take a look at the Very Brief Guide again,” Ruben said. “If whoever designed the Loop program built in a drive_train_forward and a stop_train method, maybe she also built in a way of figuring out whether the train is at a platform.”

Scarlet and Ruben returned to the Computing Contraption and looked over the Very Brief Guide to the Loop and Its Machinations. Meanwhile, the King wondered aloud: “If the loop was an infinite loop, why did the train stop for us at all? Shouldn’t it have whizzed by like it did at the other stations?”

“I don’t know,” said Ruben. “But remember how Haldo said there might have been another program in the kingdom that caused the Mysterious Pipe to overflow? Maybe there was some code running somewhere that told the Loop train to stop for us.”

“Maybe,” said the King, “but what code, and why? And who wrote it? This is getting stranger and stranger by the minute.”

“I think I’ve found something we can use,” said Scarlet. “It says here that the Loop program also has an at_stop? method. If we call that the right way, we should be able to move forward when we’re between stops, then stop when we get to a platform!”

“Great!” said Ruben. “And I think I know just how to do it.” He stepped up to the Computing Contraption and began to type.

“Don’t forget an end for your while loop,” Scarlet said. “Just like for if/elsif/else, loops need an end.”

“I know, I know,” said Ruben. “There, I think this’ll do it.”

while true

if at_stop?

stop_train

break

else

drive_train_forward

break

end

end

“Hold on just a moment,” said the King. “What’s that break bit do?”

“That tells the while loop to immediately stop,” said Ruben. “Otherwise, we’ll just stop_train or drive_train_forward forever!”

“It seems we need a way to fix that,” grumbled the King.

“I think this new code will do the trick,” Ruben said. He pressed ENTER, and the train whirred to life. In less than a minute, the train pulled into the next platform and eased to a halt.

“We did it!” said the King. “And we’re at East Bumpspark station! The Hashery is just two more stops from here, at the New Mixico platform.”

image with no caption

“Great! We’ll be there in no time,” said Ruben. But the train just sat there at the East Bumpspark station, doors open, without a soul on the platform. The King, Scarlet, and Ruben stood around awkwardly for a minute or two before the King cleared his throat to break the silence.

“Well,” he said. “It looks like we figured out how to stop the train at a platform, but it’s not restarting for some reason. Shall we take a second look at the code?”

“Way ahead of you,” Scarlet said. “And I think I know what the trouble is—in our while loop, we give the Loop program an instruction to stop if it’s at a station and to proceed if it isn’t. Well, we’re at a station, and the Loop is doing exactly what we’re telling it to do—it’s stopped! We never wrote anything in our loop to tell the train to start again after stopping.”

“You’re right!” said Ruben. “We need to rewrite the program. Maybe something like this?” And he typed:

while !at_stop?

drive_train_forward

end

“That !at_stop? looks a bit ugly to me,” Scarlet said. “And Ruby is all about writing beautiful code. Maybe something like this?” She took her turn at the Computing Contraption:

until at_stop?

drive_train_forward

end

“Just like if has an unless, while has an until,” Scarlet said. “This says that until we reach a stop, we should keep driving the train forward.”

“That does look much nicer,” said the King, “but we still have a problem: we’re currently at a stop, so the program won’t move us forward! And even if it does, we’ll just move forward to the next station and stop, with nothing in the program telling the train to start again.”

“You’re exactly right,” said Scarlet. “We need some way of telling Ruby to move from stop to stop until there are no more stops on the line. Ruben, do you see anything on the list that would tell the train to keep going from one station to the next?”

“Well,” said Ruben, “It says here that Ruby’s next method can be used in the Loop program to move from one station to another, but I’m not totally sure how we could do that. There’s an example in the Very Brief Guide to the Loop and Its Machinations, but it has all these weird-looking square brackets in it. Have you ever seen those before?”

Arrays

While Scarlet explains those funky-looking brackets to Ruben, I’ll take a minute to explain them to you. (Scarlet could have explained just as well, but I was getting a little antsy.)

What Ruben’s describing looks like this:

["East Bumpspark", "Endertromb Avenue", "New Mixico", "Mal Abochny"]

A bunch of Ruby objects between square brackets ([]) and separated by commas (,) is called an array. Arrays are basically just lists! For example, you could make a Ruby grocery list with an array, like so:

grocery_list = ["cheese", "bread", "grapes", "a festive hat for all

occasions"]

You can put anything in a Ruby array: strings, numbers, Booleans, or even other lists! This is a handy way to set a single variable equal to a whole bunch of values. We’ll talk more about arrays in the next chapter, but the important thing to know for now is that arrays can be used with really handy methods (called iterators, but don’t worry about memorizing that word right away) that let you iterate through—that is, go over—each element in the array. Examples are the best way to learn, so try this code in IRB now to see the results:

>> grocery_list = ["cheese", "bread", "grapes", "a festive hat for

all occasions"]

>> for item in grocery_list

>> next if item.length.odd?

>> puts item

>> end

This will print out:

cheese

grapes

=> ["cheese", "bread", "grapes", "a festive hat for all occasions"]

You see the whole array at the end because even though for will print only what you asked, it gives you back the whole array in case you did something to change it. (We didn’t.)

image with no caption

The next method is built into Ruby and does exactly what it sounds like: it moves on to the next item in the array immediately, without calling any other code. In this example, since the string "bread" has a length of 5 and "a festive hat for all occasions" has a length of 31 (both odd numbers), next gets called, and these items in the list don’t get printed out (remember, next goes immediately to the next item in the list, skipping any other code before its end). Since "cheese" and "grapes" each have lengths of 6—an even number—and since next is only called if the number of letters is odd, the puts statement gets called, and the item names are printed out.

As for that brand-new for/in bit you just saw, I’ll leave it to Scarlet and Ruben to explain that. It sounds like Ruben’s got the hang of arrays and iterators, so let’s check out the example he’s working on in the Loop program.

Putting Arrays and Loops into Action

“I think I understand,” Ruben said. “So arrays are just lists of things—strings, numbers, anything we like—and we can set them to a single variable name if we want. Not only that, but we can use loops and iterators to go over the entire array so we can do something for each item, or element, in the array if we want.”

“Exactly,” said Scarlet. “Can I have a look at what you’re typing in IRB?” Ruben nodded and turned the Computing Contraption’s display toward Scarlet. This is what she saw:

stops = ["East Bumpspark", "Endertromb Avenue", "New Mixico", "Mal

Abochny"]

for stop in stops

next if stop.empty?

end

“Good!” said Scarlet. “But what’s going on with that for/in part? Is that a kind of loop, like while?”

“Sort of,” said Ruben. “Basically it’s telling Ruby, ‘Hey! For each thing in this array, carry out the instructions before the end. So, in this case, for each stop in the stops list, go to the next one if the stop has no people waiting there.”

“Okay,” said Scarlet. “One more question—I saw you define the stops variable and set it to an array, but I didn’t see you assign the stop variable anywhere. Why’s that?”

“That’s just a cool shortcut Ruby lets you take. See, as you go through the array, Ruby moves from each item to the next, and it makes it a lot easier if you can give each item a temporary name while you’re working on it. Since this ‘temporary’ variable only matters inside the for loop, you don’t have to declare it—you just say something like for stop in stops, and Ruby knows that stop will take on the value of each item in the stops array in turn. In fact, you can give that variable any name you want, like item or thingy or elf_with_a_pet_ham, but stop makes the most sense, I think.”

“I think so, too,” said Scarlet. “But something about that for loop looks weird to me. I’ve read a lot of Ruby code by now, and I don’t see many for loops floating around. I do see a lot of these, though!” And she started typing into the Computing Contraption:

stops = ["East Bumpspark", "Endertromb Avenue", "New Mixico",

"Mal Abochny"]

stops.each do |stop|

next if stop.empty?

end

“Whoa!” said Ruben. “What’s that? Does it do the same thing as my for loop?”

“Yup!” said Scarlet. “And it’s only a tiny bit different, but much nicer looking. Instead of the for/in part, we can just call the each method directly on the stops variable. Then we have exactly the same code as before, only it’s between do and end instead of for/in and end. The do/endbit actually comes up a lot in Ruby, and it’s called a block.”

“Okay,” said Ruben, “that makes sense. But what about the stop between the two vertical lines? Is that like the ‘temporary’ stop variable from my for loop?”

“Exactly,” Scarlet said. “You can think of those vertical lines as being like the sides of a little window that we move along the array: as we put the box over each element in the array, stop is temporarily set to the value of that element.”

“In fact,” she continued, “You can even write it a bit shorter. Ruby lets you use curly brackets instead of do/end, and since we have only one line of code in our block, it looks even more elegant with the brackets.” She typed into IRB:

stops = ["East Bumpspark", "Endertromb Avenue", "New Mixico",

"Mal Abochny"]

stops.each { |stop| next if stop.empty? }

“This is all very fascinating,” said the King, “but will the code work? Will we be able to get to New Mixico station before we all starve to death, or—heaven forbid!—the Hashery runs out of hash?”

“I think we’re all set!” said Ruben. “And I have a feeling we’ll talk a lot more about arrays and blocks after we’ve had a good meal. Ready, Scarlet?” Scarlet nodded, and on the count of three, they pushed ENTER together. The Loop train car vibrated to life, the doors whooshed shut, and the car moved on to the Endertromb Avenue station. The three held their breath as the car doors opened, the car idled . . . and the doors slid shut again! The King began to clap as the car moved on to New Mixico station, and he didn’t stop clapping until they left the train, walked down the stairs leading from the platform to the street, and made their way toward the cherry-red doors of the Hashery.

Your Project, Should You Choose to Accept It

After a careful review of the Loop by the Loop Authority Council for the King, the members of the Council have determined that the Loop does, in fact, need a conductor (if only to look after the program and ensure it doesn’t end up in any more infinite loops). Surprisingly, no one volunteered for the position, so I went ahead and volunteered you! That’s just the sort of guy I am.

Conducting trains is big business, but I think it’s safe to start small. We’ll just work on a program to report whether the Loop stops at a requested station, and if it does, list all the stops before the requested stop so passengers will know how many stations to expect before theirs. Let’s begin by making a new file called loop_the_loop.rb. (As always, peek back at Chapter 1 if you don’t remember how to do this, or ask your local adult for help.) Then open your file and type the following code.

loop_the_loop.rb

➊ we_wanna_ride = true

stops = ["East Bumpspark", "Endertromb Avenue", "New Mixico",

"Mal Abochny"]

➋ while we_wanna_ride

print "Where ya headin', friend?: "

➌ destination = gets.chomp

➍ if stops.include? destination

puts "I know how to get to #{destination}! Here's the station list:"

➎ stops.each do |stop|

puts stop

break if stop == destination

end

else

puts "Sorry, we don't stop at that station. Maybe another time!"

we_wanna_ride = false

end

end

There are a few new bits here, but nothing you can’t handle!

First, we set a couple of variables: we_wanna_ride is true, and stops is set to an array of strings ➊. Next, we create a while loop with we_wanna_ride (which starts as true) as the condition ➋. Inside the loop, we use print to print some text on the screen and gets.chomp to get the user’s answer ➌.

The include? method ➍ is new! It simply returns true if the array has an element that matches destination and false otherwise. (This is really handy for quickly checking whether an object you want is in a given array.)

The next part at ➎ is a little trickier:

stops.each do |stop|

puts stop

break if stop == destination

end

You’ve already seen the stops.each do |stop| ... end part, and the break if stop == destination part does exactly what you’d guess: it breaks out of the loop as soon as the Loop reaches a stop that equals the destination the passenger wants. It prints out each element before it makes this check, though, so it will always print out at least one stop if that stop is in the array.

You can test out your conductor program by typing ruby loop_the_loop.rb at the command line and pressing ENTER. You should see something like this (of course, you’ll probably pick different stops than I did):

Where ya headin', friend?: Mal Abochny

I know how to get to Mal Abochny! Here's the station list:

East Bumpspark

Endertromb Avenue

New Mixico

Mal Abochny

Where ya headin', friend?: New Mixico

I know how to get to New Mixico! Here's the station list:

East Bumpspark

Endertromb Avenue

New Mixico

Where ya headin', friend?: Detroit

Sorry, we don't stop at that station. Maybe another time!

You can keep rerunning it with different input to see how the output changes each time!

If you want to make your program even more elaborate, here are some other things to think about:

§ Right now, the program will keep prompting the user for input as long as the user keeps asking for train stops that are in the stops array. How might you update the program to run only once, even if it recognizes a stop?

§ How could you handle accepting lowercase letters for destinations? (Hint: This will be a lot like one of the extra steps you might have taken for your last project.)

§ What if a passenger is going the other way on the train (for instance, from Mal Abochny to East Bumpspark)? How could you update your program to work in both directions? Even trickier, what if the train route is a big circle (meaning if a passenger goes from East Bumpspark to Mal Abochny, the next stop after Mal Abochny should be East Bumpspark again)? How could you update your program to print out the right list of train stops if a passenger wants to go all the way around the circle?

You Know This!

I threw a lot at you in this chapter, but if you’re conducting trains after reading it, I’m pretty sure you know your stuff. Let’s review what we looked at.

We covered while loops, which are loops that contain some code between while and end and will continue to run that code as long as the while condition is true. (Beware—if there’s no way for the condition to become false, the loop will go on forever and create an infinite loop!)

We saw that, just like if has an unless, while has an until. If you can write:

while something_is_the_case

# Do something!

end

then you can also write:

until !something_is_the_case

# Do something!

end

We also saw that when using a loop or an iterator (which is just Ruby code that loops over items in a list), we could call the next method to skip certain elements based on an if/elsif/else or unless statement.

We talked a little bit about arrays, which are basically just Ruby lists, and how we can put anything we want inside them. An array looks like this:

my_hobbies = ["Ruby", "eating things", "cat videos"]

We learned that we could use a for loop or the each method to iterate, or go over, an array, and while they work exactly the same, the each method is more common in Ruby.

A for loop looks like this:

# Assuming we have an array called todo_list

for task in todo_list

puts task

end

And iterating with each looks like this:

# Using do/end

todo_list.each do |task|

puts task

end

# Or, using curly brackets

todo_list.each { |task| puts task }

Finally, we learned a bit about blocks. Ruby blocks are just regular Ruby code sandwiched between either do/end or curly brackets ({}). Some methods, like each, take blocks, and we’ll learn way more about those after a hearty helping of Ruby know-how at the Hashery.