The (Chunky) Bacon to Ruby's Hash - Ruby Wizardry: An Introduction to Programming for Kids (2014)

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

Chapter 6. The (Chunky) Bacon to Ruby's Hash

Symbols!

“That hash was amazing!” Scarlet said. Ruben nodded vigorously as he shoveled another helping of eggs and hash into his mouth.

“I’m glad you liked it!” said Big Hank. “But that mid-morning rush’ll be here any minute, and we need to get cracking if we’re going to be ready for it.”

image with no caption

Scarlet jumped down from her stool. “We have eggs to fry up, potatoes to grate, sausages to cook, breakfast gravy to make, and biscuits to bake. Anything else?”

Hank twirled his mustache. “I’m not sure,” he said. “Let’s have a look at that menu you kids and Squeaky Jim cooked up.”

“Sure!” Scarlet said, and she called up the Hashery menu on the kitchen’s Computing Contraption:

>> hashery_menu

=> { :eggs => 2, :hash => 3,

:jam => 1,

:sausage => 2,

:biscuit => 1..3 }

“This looks good—each order of food is associated with its price in a hash,” said Big Hank, “but we should put our breakfast beverages on there, too. Can you add a key with an array as a value to my menu hash?”

“Of course,” Scarlet said. “What drinks should we put in it?”

“We’ve got coffee, orange juice, and tea,” said Hank.

“Okay!” Scarlet said. She typed:

>> hashery_menu['drinks'] = ['coffee', 'orange juice', 'tea']

=> ["coffee", "orange juice", "tea"]

“Aha! So that’s how you add a key to a hash,” Hank said.

“Yup!” Scarlet replied. “You just type the hash name, then the key name between square brackets—here, we’re using 'drinks'—and set the whole thing equal to whatever value you like. See how we updated the hashery_menu?”

>> hashery_menu

=> {:eggs=>2, :hash=>3, :jam=>1, :sausage=>2, :biscuit=>1..3,

"drinks"=>["coffee", "orange juice", "tea"]}

“Cool!” said Ruben, who had finally finished eating his eggs and hash. “Now we have a list of drinks on the menu.” He leaned in close to the glowing screen of the Computing Contraption. “But it looks like the drinks hash key is a string, and the rest are symbols. Does that make a difference?”

“Oh boy, does it!” said Squeaky Jim, who had been opening bags of potatoes and cleaning the Hashery’s enormous Grate-O-Matic. He pushed his paper hat farther back on his head and leaned against the machine. “You see—” he began, but as he started to speak, his elbow pushed down the machine’s huge switch, turning it on. It roared to life, scaring the sweet peas out of Squeaky Jim and nearly causing him to fall over multiple times as he scrambled to turn it back off.

image with no caption

“You see,” Jim squeaked after he finally shut down the Grate-O-Matic, “even though I’m not very good at Ruby, I have tried to program the kitchen’s Computing Contraption every now and again. One morning, the Hashery was unbelievably busy—one of the biggest mid-morning rushes I’d ever seen!”

“I remember that one,” Big Hank said, pulling an armful of sausage links out of a shiny red refrigerator. “Not only did we have a ton of customers, but we were doing a Build Your Own Menu day.”

“Build Your Own Menu day?” Ruben asked, scratching his head.

Big Hank nodded and began yanking sausages off the long chain of links and tossing them into an enormous skillet. “Yup. We let customers create their own personal menus, so they could order anything they wanted. We were okay at first—people were building their menus, ordering food, and getting served. But as the morning wore on, the program got slower and slower. By the peak of the rush, we could barely get any orders through! We had to shut down the kitchen’s Computing Contraption and do all the orders by hand. It was chaos.”

Squeaky Jim nodded. “And I think I know why!”

Hank stopped pulling apart sausage links. “You do?”

“Yup!” Jim said. “I was reading up on Ruby the other day to try to get a bit better at running the kitchen, and I found out that Ruby symbols use up less memory than strings. We were using strings for all the keys in our hashes during Build Your Own Menu day, and as the program went on and on, it used more and more memory until it didn’t have enough to do its job.”

“Back up a bit,” said the King, who was chewing thoughtfully on a raw potato. “What exactly are these Ruby symbols? And what do you mean when you say they use less memory than strings?”

The Skinny on Symbols

While Squeaky Jim tries to explain Ruby symbols to the group, I’ll give you the rundown. Basically, a Ruby symbol is just a name. For instance, if I’m talking about the King and Scarlet is talking about the King, we’re both talking about the very same thing—the King! When we talk about symbols (that is, names) in Ruby, we write them with a colon in front, like :the_king. You’ll often see the underscore (_) in symbol names because, just like variable names, they aren’t allowed to have spaces in them.

So how is a symbol different from a string, like 'The_King'? Well, think back to the King’s string from Chapter 2. Now imagine that the King has two strings with exactly the same beads and trinkets on them. While they might have the same contents, they aren’t the exact same thing. But when we both talk about the King, we’re not talking about two kings who look exactly like each other: we’re talking about one and the same king!

If you’re still a bit confused, never fear: I’ve got a couple of code examples that should help clear things right up. Fire up IRB and try this on for size:

>> string_one = 'The King'

=> "The King"

>> string_two = 'The King'

=> "The King"

>> string_one.object_id

=> 2184370320

>> string_two.object_id

=> 2184365180

Here we’re setting two different variables to the same string value of 'The King'. Then, when you use the object_id method on these two variables, you’re asking Ruby to provide the unique number it uses to keep track of every object in a running Ruby program. It’s a kind of ID number that Ruby uses to tell objects apart, and no two objects have exactly the same one. On the flip side, if two variables have the same object ID number, they must be talking about the very same object.

The object ID numbers you see in IRB won’t be quite the same as mine, but that’s okay! Object IDs get reassigned every time you start a new Ruby program. The important thing is that string_one and string_two, even though they’re both equal to 'The King', are different objects. Their contents are exactly the same, but just as with our example of the King’s string, we’re talking about two completely different strings that just happen to have the same thing inside.

Now check this out:

>> symbol_one = :the_king

=> :the_king

>> symbol_two = :the_king

=> :the_king

>> symbol_one.object_id

=> 466088

>> symbol_two.object_id

=> 466088

Here we’re setting two different variables, symbol_one and symbol_two, to the symbol :the_king. Again, your object IDs won’t be exactly the numbers just shown, but when you compare your object IDs for symbol_one and symbol_two, you’ll see that they’re the exact same number! Just like how when we’re talking about the King, we’re talking about the very same person, both symbol_one and symbol_two are talking about the exact same object, :the_king.

Because symbols are simply names you can toss around, you don’t assign values to them. While you can definitely say:

>> variable_name = :my_fancy_symbol

=> :my_fancy_symbol

you can’t say:

:my_fancy_symbol = some_value

If you try this, you’ll get a SyntaxError. Just as you can’t assign a different value to a string or a number by putting it on the left-hand side of the equal sign, you can’t assign a different value to a symbol, either.

The only time you’ll have symbols on the left-hand side is when you use them in hashes, like this:

>> fancy_words = { bloviate: 'To talk at length' }

=> {:bloviate=>"To talk at length"}

Remember, we don’t need to start our bloviate key with a colon if we use the newer hash syntax. If we want to use the older hash rockets (=>), we start the symbol with a colon:

>> fancy_words = { :bloviate => 'To talk at length' }

=> {:bloviate=>"To talk at length"}

But yes! I do go on. What you’re probably wondering is: what are symbols good for? How is it that they use less memory than strings?

Because a symbol always has only one object ID, it gets created only one time in any given Ruby program. That means you could have a thousand million billion variables that are all set to a certain symbol, and only one symbol object gets created. If you were to do that with strings, they’d all have different object IDs, so you’d get a thousand million billion different strings. Just like you, Ruby has only a certain amount of memory and can keep track of only so many things at once. If you create a huge number of strings, Ruby will start to run out of memory as it tries to juggle them all, and it will slow way down. Your program might even crash! If you use symbols, Ruby will create fewer objects and use less memory, so programs that use symbols for things like hash keys can run faster than equivalent programs that use strings. This brings us to the thousand-million-billion-dollar question: when should you take advantage of the savings symbols offer?

Basically, any time you need to use a name over and over but don’t want to create a brand-new string each time, symbols are the way to go. They’re very good for hash keys, and they’re also useful for referring to method names. We’ll talk about using symbols for method names very soon!

image with no caption

Speaking of soon, I’m pretty sure Squeaky Jim is about to wrap up his explanation of Ruby symbols. Let’s see if Ruben, Scarlet, the King, and Big Hank have as good an understanding of them as you do!

Symbols and Hashes, Together at Last

“I think I get it,” Ruben said. “Symbols are just names that Ruby uses to refer to one particular object, so if we use symbols as keys in hashes, we’re really just referring to the same object over and over.”

“That’s exactly right!” said Squeaky Jim. “Now you see why we had such a terrible time using strings instead of symbols in our Build Your Own Menu hashes.”

“Of course!” Scarlet said. “Each time a customer made a new menu, it made a whole bunch of new strings.”

“We had hundreds of customers,” Big Hank sighed. “No wonder our Ruby program ran out of memory!”

“Well, I certainly don’t want to start adding strings into the menu now,” Scarlet said. “How can we change the string key to a symbol?” She typed hashery_menu into the Computing Contraption to call up the Hashery menu’s contents:

>> hashery_menu

=> { :eggs => 2, :hash => 3,

:jam => 1,

:sausage => 2,

:biscuit => 1..3,

"drinks" => ["coffee", "orange juice", "tea"] }

“Hmm,” said the King. “Can we just change the string key to a symbol key?”

“I don’t think so,” said Squeaky Jim. “From what I’ve read, I think the best thing we can do is to delete the string key, then replace it with a symbol key.”

“You may be right,” Ruben said, “but programming is all about experimenting. I’ve heard that Ruby has a to_sym method that will turn a string into a symbol. Should we give it a try?”

“Sure thing,” Scarlet said, and she typed:

>> hashery_menu.keys.last.to_sym

=> :drinks

“It looks like it worked!” Ruben said. “Can you bring up the hash again to be sure?”

Scarlet nodded and brought up the Hashery menu again.

>> hashery_menu

=> { :eggs => 2, :hash => 3,

:jam => 1,

:sausage => 2,

:biscuit => 1..3,

"drinks" => ["coffee", "orange juice", "tea"] }

“Darn!” Ruben said. “Ruby returned a symbolized version of the string 'drinks', but it didn’t actually change the key in the hash.”

“That’s probably just as well,” said Big Hank. “I’ve been thinking about our breakfast beverages, and I realized we don’t have their prices in the array at all!”

Scarlet slapped her forehead. “That’s right!” she said. “We need to have the drinks and the prices in there.” She thought for a minute. “Wait—if we’re associating drinks and their prices, that’s just like associating each food item with its price. Can we put a hash inside another hash?”

“Nothing to do but experiment!” said the King. “Why don’t you go ahead and delete the "drinks" key like Jim suggested, and then try adding a symbol key with a hash as the value?”

“Okay!” said Scarlet. “Jim, do you know how to delete a key from a hash?”

“I think so,” Jim said, and he reached over and began typing into the Computing Contraption:

>> hashery_menu.delete('drinks')

=> ["coffee", "orange juice", "tea"]

“Whoa, what was that?” said Ruben. “When you deleted the key, it gave you the value back!”

Jim nodded. “That’s what the delete method does!” he said. “That way, if we had wanted to use the value of the deleted key for something, we could have saved it into a variable, like this:

menu_drinks = hashery_menu.delete('drinks')

“But,” Jim said, “we can’t do that now, because the 'drinks' key is gone. See?” He typed again:

>> hashery_menu

=> { :eggs => 2, :hash => 3,

:jam => 1,

:sausage => 2,

:biscuit => 1..3 }

“Nice work!” Scarlet said. “Now all we have to do is test whether we can put a hash inside a hash. Big Hank, what are the prices I should use for the drinks?”

“It’s a dollar for coffee, two for orange juice, and one for tea,” Hank said. Scarlet typed into the Computing Contraption:

>> hashery_menu[:drinks] = { :coffee => 1, :orange_juice => 2,

:tea => 1 }

=> { :coffee => 1, :orange_juice => 2, :tea => 1 }

“It worked!” shouted the King. “Great work, everyone!”

“And just in the nick of time!” boomed Big Hank. The group had been so busy crowding around the Computing Contraption and working on getting the Hashery menu just right, they hadn’t noticed a steadily growing din. Voices filled the air as customers crowded into the Hashery, and even Big Hank had to shout to be heard above them all: “Spin up the Grate-O-Matic! Attend to the skillets! Bake biscuits like your lives depend on it! The mid-morning rush is upon us, and they’re hungry!”

“Aye aye, Hank!” said Squeaky Jim, who not only didn’t squeak but spun up the Grate-O-Matic and began churning out hash like he’d done it all his life. “Let’s get that new menu out to all the customers!”

“The menu! I almost forgot,” Hank said. “We do have one more addition to today’s specials.” And he typed:

>> hashery_menu[:chunky_bacon] = 1

=> 1

“Chunky bacon?” Scarlet and Ruben asked together.

image with no caption

Hank smiled and shrugged. “Friend of mine used to come in all the time and order it,” he said. “Haven’t seen him in a while, so I haven’t had it on the menu. But who knows?” He looked out at the growing crowd of hungry Hashery customers. “Maybe today’s the day he’ll come back.”

The Mid-morning Rush

Now that you know all about symbols, you can handle a mid-morning rush of any size and not worry about slowing down your Ruby program or running low on memory. In fact, you’re such a wizard with Ruby symbols that Big Hank and Squeaky Jim have given you a mission that they’ve so far found impossible: transforming their old Build Your Own Menus so that they use symbols for keys instead of strings!

This may sound like an odd idea at first, but it’s just to help make sure you’re comfortable using Ruby hashes; you don’t have to change all your keys to symbols every time you use them. Hashes are great for storing information like our Hashery menu, and you’ll use them time and again whenever you write Ruby—not just for the code in this book.

Earlier, we saw that we couldn’t just call to_sym on a hash key and expect it to magically change; instead, we had to delete the key and replace it.

That works okay for a single string key, but Hank and Jim are talking about a thousand million billion strings in hundreds of customer menus—you could never convert them one at a time, even if you wanted to! But what if we could automatically run through a hash and do exactly that: grab each string key, delete it, save the key’s value, and assign that value to a new symbol key?

Let’s make a new file called strings_to_symbols.rb. (As always, peek back at Chapter 1 if you don’t remember how to do this, or ask the nearest adult to help you.) Then open your file with your text editor, and type the following:

my_own_menu = { 'tater_tots' => 2,

'fancy_toast' => 3,

'omelette' => 3,

'tiny_burger' => 4,

'chunky_bacon' => 1,

'root_beer_float' => 2,

'egg_nog' => 2

}

Here, we’re creating a brand-new hash called my_own_menu and assigning some values (the prices, which are numbers) to some keys (the menu items, which are strings). Keep on adding to the program; we’re not done yet!

puts "Object ID before: #{my_own_menu.object_id}"

Next, we’ll print out the object ID of our menu hash. This is so we can later confirm that although we’ve made some changes to our hash, it’s still the same object; after we make changes to our keys and values, if the ID is the same as it was before, we’re talking about the very same hash, just with some different information in it.

Let’s keep adding to strings_to_symbols.rb. We’ve got strings for hash keys now, but what we really want are symbols! We’ll need to add a bit more code to change our string keys to symbol keys.

my_own_menu.keys.each do |key|

my_own_menu[key.to_sym] = my_own_menu.delete(key)

end

puts "Object ID after: #{my_own_menu.object_id}"

puts my_own_menu

Okay, that does it. We call the keys method on the my_own_menu hash to get our keys, then immediately call each on those keys to iterate over them. (Remember that from Chapter 5? Feel free to look back if you need a reminder.)

Here’s the really cool part: for each key in the hash, we call delete on the key (which removes it from the hash), but since delete returns the value associated with the key that was deleted, we immediately set this equal to calling to_sym on the key. This is an amazing double whammy: it deletes the original key from the hash while immediately adding the value to a new key, and that new key is just the original one turned into a symbol. The result? You change all the keys in your hash from strings to symbols!

We can even prove it’s the very same hash, not a copy: we puts the hash’s object ID before and after iterating over it, and you’ll see in the output that the object ID is the very same both times. That’s right—every object in Ruby has an object ID, including the hash itself !

Your complete code should look like this:

strings_to_symbols.rb

my_own_menu = { 'tater_tots' => 2,

'fancy_toast' => 3,

'omelette' => 3,

'tiny_burger' => 4,

'chunky_bacon' => 1,

'root_beer_float' => 2,

'egg_nog' => 2

}

puts "Object ID before: #{my_own_menu.object_id}"

my_own_menu.keys.each do |key|

my_own_menu[key.to_sym] = my_own_menu.delete(key)

end

puts "Object ID after: #{my_own_menu.object_id}"

puts my_own_menu

Go ahead and run your code—type ruby strings_to_symbols.rb and press ENTER. The output should look like this:

Object ID before: 2174149520

Object ID after: 2174149520

{:tater_tots=>2, :fancy_toast=>3, :omelette=>3, :tiny_burger=>4,

:chunky_bacon=>1, :root_beer_float=>2, :egg_nog=>2}

You should see the very same object ID printed twice, then a beautiful printout of your hash, complete with symbols for keys instead of strings.

What Else Can You Do with Symbols?

Now that you can solve all of Big Hank and Squeaky Jim’s menu woes with ease, you might be wondering what else you can do. As Ruben said, experimenting is a huge part of programming, and there’s a lot of experimentation you can do with hashes and symbols. For example, what happens if you call to_sym on a string that contains a space? (You’ll still get a symbol, but it will look weird—try it!)

We can also explore hashes within hashes. Remember, we can access a value in a hash like this:

>> hash_name[:key]

=> value

So how might you go about accessing the value of a hash inside a hash? Here’s a hint—for our original menu:

>> hashery_menu

=> { :eggs => 2, :hash => 3,

:jam => 1,

:sausage => 2,

:biscuit => (1..3),

:drinks => { :coffee => 1, :orange_juice => 2, :tea => 1 } }

what do you think hashery_menu[:drinks][:orange_juice] will give us back?

Finally, strings have a to_sym method that turns them into symbols, but symbols also have a to_s method (short for “to string”) that turns them into strings. How might you update this program to change symbol keys to strings?

You Know This!

We only really talked about hashes and symbols in this chapter, but since they’re not as easy to understand as numbers or strings (or even arrays), they’re worth going over one more time. (Heck, I’ve been writing Ruby for years, and I still think symbols are weird!)

First, we looked at how to add a key and a value to a hash, which is as easy as setting a key in square brackets ([]) equal to a value:

my_hash[:key] = value

Next, we introduced symbols, which are basically just names; you don’t assign values to them, but you can store them in variables if you want to.

For example, this is okay:

my_variable = :my_symbol

But this will cause an error:

:my_symbol = some_value

The only time symbols can appear on the left-hand side is when we’re using them as hash keys, like so:

>> my_hash = { ninjas: 'awesome',

>> wizards: 'pretty rad',

>> warrior_princesses: 'super tough'

>> }

=> {:ninjas=>"awesome", :wizards=>"pretty rad", :warrior_

princesses=>"super tough"}

When you’re talking about the King or your teacher or Abraham Lincoln, you’re always talking about exactly the same person; likewise, symbols always refer to exactly the same object. This means they use less memory than strings, because whenever you create a new string—even if it’s all the same letters as another string—it’s a separate object with its own object ID:

>> symbol_one = :the_king

=> :the_king

>> symbol_two = :the_king

=> :the_king

>> symbol_one.object_id

=> 466088

>> symbol_two.object_id

=> 466088 # The same!

>> string_one = 'The King'

=> "The King"

>> string_two = 'The King'

=> "The King"

>> string_one.object_id

=> 2184370320

>> string_two.object_id

=> 2184365180 # Different!

In general, symbols are good for when you need to use a name over and over, mostly for hash keys and a couple of other neat tricks (which we’ll talk more about in later chapters). When you care about the content of something, you want a string; when you care about the name of a thing, you want a symbol.

If you’re ever unsure whether two objects are the same or different, you can always use the object_id method (which works on any Ruby object) to get an object’s ID number. Every object has its very own ID number, which is how Ruby keeps track of which object is which during a program:

>> 'The King'.object_id

=> 2187090900

>> { :eggs => 2, :hash => 3 }.object_id

=> 2187097060

>> ['eeny', 'meeny', 'miny', 'moe'].object_id

=> 2187104080

Remember, your object IDs won’t be exactly the same as the ones shown here, but they should all be different from one another on your computer.

Converting between symbols and strings is a snap! You can use the to_sym method to turn a string into a symbol:

>> 'drinks'.to_sym

=> :drinks

And you can use the to_s method to turn a symbol into a string:

>> :drinks.to_s

=> "drinks"

When it comes to deleting keys from hashes, you not only saw that you can do it with the delete method, but you also learned that delete both removes the key-value pair from the hash and returns the value, so you can save it in a variable if you want:

>> simple_hash = { :one => 1, :two => 2 }

=> { :one => 1, :two => 2 }

>> saved_value_from_hash = simple_hash.delete(:two)

=> 2

>> simple_hash

=> { :one => 1 }

>> saved_value_from_hash

=> 2

Finally, you learned that it’s 100 percent allowed to store a hash inside another hash, like so:

>> fancy_hash = { :number_key => 42,

>> :hash_key => { :first_value => 1,

>> :second_value => 2

>> }

>> }

=> {:number_key=>42, :hash_key=>{:first_value=>1, :second_value=>2}}

You’re well into the thick of Ruby now! The good news is that it’s pretty much all smooth sailing from here. While there are a few tricky concepts ahead, once you’ve mastered the basic Ruby objects (like numbers, strings, arrays, and hashes), learned how to use a bunch of their methods, and become a whiz at topics like control flow (using if/elsif/else, loops, and iterators), you’ve covered most of the language. Don’t worry if you don’t feel perfectly comfortable with Ruby yet; while it doesn’t take long to learn the basics, you can take as much time as you want to explore the depths. And that’s where we’re going next: deeper into the heart of Ruby, where strange-sounding (but powerful!) creatures await.