Arrays
An array, or list, is very much like a string in some ways: It is a container whose elements can be referred to by their position and whose elements can be examined and changed using the same techniques you have just learned. But there is a big difference between the two. An array can hold objects of any type, not just characters.
Instead of the quotation marks that you use to create a string, surround a new array with square brackets. Elements of an array are separated by commas.
veggies = ["corn", "carrots"] small_primes = [2, 3, 5, 7, 11] some_objects = ["a string", 44, self]
How would you find the second element of the veggies array? Knowing what you know about string objects, you should be able to guess. (Remember, we start counting from zero.)
veggies[1] #> "carrots" veggies[1] = "lettuce" # veggies == ["corn", "lettuce"]
Arrays grow as needed, just as strings do. New storage slots are created on demand, with nils inserted when necessary.
veggies[2] = "peas" # veggies == ["corn", "lettuce", "peas"] veggies[8] = "squash" # veggies == ["corn", "lettuce", "peas", nil, nil, nil, nil, nil, "squash"]
We've seen that for strings, the [index,length] notation specifies substrings; for arrays, it specifies subarrays.
n = [39, 38, 12, 6, 5] n[2,3] #> [12, 6, 5] n[2,1] #> [6]
You can replace subarrays in much the same way you learned with substrings. The lengths of the new and old subarrays don't have to match, so subarrays can be deleted by replacing them with nothing (that is, nil), and they can be inserted by "replacing" a subarray whose length is zero.
n[2,2] = [100, 101] # n == [39, 38, 100, 101, 5] n[1,1] = [200, 201, 202] # n == [39, 200, 201, 202, 100, 101, 5] n[4,2] = nil # n == [39, 200, 201, 202, 5] n[2,0] = ["two strings", "inserted"] # n == [39, 200, "two strings", "inserted", 201, 202, 5]
When defining an array of strings that contain no spaces, you will find that the following syntax sugar saves wear on your fingers and keyboard. The w stands for words.
%w(black gray green white blue orange) # ["black", "gray", "green", "white", "blue", "orange"]
Arrays understand the << and + methods just as strings do. In fact, if you go through the string methods and try each of them on arrays, you'll find that all the ones that make sense for arrays will do what you expect them to, while those with specific meanings that apply only to strings are rejected.
a1 = [10, 20, 30] #> [10, 20, 30] a2 = %w(a b) #> ["a", "b"] a2 << "c" #> ["a", "b", "c"] a3 = a2 + a1 #> ["a", "b", "c", 10, 20, 30] a3.include? ("b") #> true a1.include? ("b") #> false a3.index (20) #> 4 a3.capitalize # error
Arrays Containing Arrays
If an array can hold any kind of object, there's no reason it can't hold another array.
pocket = ["wallet", ["house key", "garage key"], "watch"]
The second element of pocket is a collection of keys, presumably on a key ring. How would you refer to the first key in that collection? If it isn't clear at first, assign a name to the key ring.
key_ring = pocket[1] #> ["house key", "garage key"] key_ring[0] #> "house key"
That's clear, but a little wordy. Unless we expect to refer to the key ring repeatedly in the future, there is no need to give it a special name now. Since they refer to the same thing, you should be able to substitute pocket[1] for key_ring. It might feel safer to parenthesize when doing this sort of thing, but in this case it's not necessary.
(pocket[1])[0] #> "house key" pocket[1][0] #> "house key"
Hardly anyone uses the parentheses for multiple indices, but you can use them, or else just visualize them, if you ever get confused about the order in which to write them. For instance, a square matrix can be expressed as an array of arrays, where each "inner" array corresponds to a row:
# The matrix we want: # 11 12 13 # 14 15 16 # 17 18 19 m = [ [11,12,13], [14,15,16], [17,18,19] ]
Suppose you want the item in the last column and first row. Which do you say: m[0][2] or m[2][0]? If you remember that the inner arrays are rows, then you know that m[0] is the first row, and so (m[0])[2] is what you want.
m[0][2] #> 13
It so happens that Ruby is supplied with a powerful Matrix class. Later in the book we'll also be defining our own matrix class, which will be somewhat simpler than Ruby's.
You can flatten an array if you want to ungroup the arrays within it. This can put all of m's matrix elements in a straight line, and it can take your keys off their ring so that they jangle around loose in your pocket.
m.flatten #> [11, 12, 13, 14, 15, 16, 17, 18, 19] pocket.flatten #> ["wallet", "house key", "garage key", "watch"]
We'll list some other useful array methods in a moment.
FIFOs and Stacks
FIFO stands for first in, first out. Conceptually, a FIFO is a one-way pipe: Objects go in one end and come out the other, always in the same order they went in. Ruby offers four methods for implementing variable-length FIFOs, which are push, pop, shift, and unshift; but you need only two of them. push and shift will suffice.
pipe = [] # empty array; same as Array.new # "push" objects in to the right end ... pipe.push("one") # pipe == ["one"] pipe.push("two") # pipe == ["one","two"] pipe.push("three") # pipe == ["one","two","three"] # ... then "shift" them out from the left end. x = pipe.shift #> "one" y = pipe.shift #> "two" z = pipe.shift #> "three"
Instead of using push and shift, we could use unshift and pop. The array would be organized backward, but in the end the values found in x, y, and z would be the same.
Why such odd names for these method pairs? Because they're not really pairs. push and pop go together, and were designed for implementing stacks. shift and unshift were designed for dealing with command-line arguments. We will leave the command line aside for now, but we mustn't neglect stacks.
Stacks
Consider a top-loading cafeteria plate dispenser, equipped with a spring so that a single plate is always accessible at the top. The order in which plates are dispensed is exactly the opposite of what you observe in a FIFO. The plate you take off the top had to be the last one put in, often fresh from the dishwasher (ever notice how warm it is?), and conversely, the first plate lowered into the dispenser cannot be taken out until all the ones above it are gone. This is last in, first out behavior.
We don't need a fresh example to illustrate a stack. If you modify the FIFO example in the preceding section by replacing each shift with a pop, then you'll have a stack. At the end you'll find x=="three", y=="two", and z=="one".
A Few Useful Array Instance Methods
min, max
Return the smallest or largest element, respectively.
[3,5,2,4].min #> 2 %w(Underhill Zamboni James).max #> "Zamboni"
uniq
Return a copy with only unique elements.
[1,2,2,3,3,3,4,4,4,4,5,5,5,5,5].uniq #> [1, 2, 3, 4, 5]
compact
Return a copy with all nil elements removed.
a = Array.new #> [] a[6] = "r" # a == [nil, nil, nil, nil, nil, nil, "r"] a.compact #> ["r"]
sort
Return a sorted copy. Later we'll learn how to specify custom sorts.
%w(Underhill Zamboni James).sort #> ["James", "Underhill", "Zamboni"]
&, |
Perform intersections and unions for sets.
s1 = [1,2,3,4,5,7,9] s2 = [2,4,5,6,7,8,9] s1 & s2 #> [2, 4, 5, 7, 9] s1 | s2 #> [1, 2, 3, 4, 5, 6, 7, 8, 9]
grep(/description/)
We'll wait until Day 8 to see the full power of this method (and to explain why the description is surrounded by slashes instead of quotes), but for now we can at least make it show us array elements that contain a specific substring.
%w(knee ankle foot leg knuckle toe elbow).grep (/le/) #> ["ankle", "leg", "knuckle"]