Summary
Ruby's container classes group items in convenient ways. We looked at four such classes today.
Arrays and hashes are general-purpose containers that accommodate every kind of object. The contents of an array are arranged sequentially and referred to by index, whereas the values in a hash are not maintained in a predictable order and are referred to by objects called keys.
The others are special-purpose containers. Strings can hold only characters. In many ways, strings act like arrays, but they also have some specialized methods, like upcase, that are just for text. Ranges might not be containers in a strict sense, but they describe increasing sequences of numbers and can be treated basically like arrays.
Exercises
Suppose you have string1 and string2, and you want to find out whether string2 is a substring of (that is, can be found within) string1. Can you do it without using the include? method?
Should this expression evaluate to true or false?
How could we get an ordered list of all the keys and values of a hash, with no duplicates? For example, how could we get this:
["abc", "def", "ghi"].include?("h")
[1,2,3,6,8]
from this:
{1=>8, 3=>6, 8=>2, 6=>2}
Answers
-
One way to do it would be with the index method. If string2 is a substring of string1, then string1.index(string2) will return a number; otherwise it will return nil. Another way would be to look at the result of string1[string2].
-
It may surprise you that this is false. Consider that the include? method is a message given to an object. What is the object? It's not a string, but an array. The array only looks to see whether it has an element exactly matching "h". It would be beyond its competence to delve into the question of substring matching; that's a job for string objects. Arrays shouldn't concern themselves with it.
-
First, it should be mentioned that the problem makes sense only when the keys and values are all of a type that can be compared with each other. In this example, they are all numbers, which is fine. If they were all strings, that would work too. A hash like {"six"=>6, "four"=>4} isn't suitable for the problem; we could do everything but the sorting.
On the other hand, (["abc", "def", "ghi"][2]).include?("h") does correctly return true, because it is the string "ghi" that is the receiver of the include? question.
That being said, we can take this one step at a time. Let's leave the "ordered" part for last, and start by turning the hash into an array:
h = {1=>8, 3=>6, 8=>2, 6=>2} h.to_a #> [[8, 2], [1, 8], [6, 2], [3, 6]]
We want keys and values all mixed together, so break up those inner arrays:
(h.to_a).flatten #> [8, 2, 1, 8, 6, 2, 3, 6]
Get rid of the duplicates:
((h.to_a).flatten).uniq #> [8, 2, 1, 6, 3]
We're almost done:
(((h.to_a).flatten).uniq).sort #> [1, 2, 3, 6, 8]
This kind of method chaining is common in Ruby, and it turns out that the parentheses aren't required; methods are applied in order from left to right.
h.to_a.flatten.uniq.sort
You can read that as "convert to array, flatten it, select only unique elements, and then sort."
Do you expect it to matter what order you write those four methods in? You can experiment to satisfy yourself.