Arrays: ordered lists you can question
Building lists, reaching into them, and the methods that turn "loop over this and collect" into a single readable line.
A list, with positions
An array is an ordered list written between brackets: [72, 95, 60]. Positions start at zero, so scores[0] is the first and scores[-1] counts from the end — the negative index being Ruby's small courtesy for "the last one" without a length calculation. scores[1..2] takes a slice by range.
<< appends and returns the array, pop removes the last item, first and last look at the edges, and length counts. Arrays grow as needed: there is no size to declare and no capacity to manage, which is why they end up holding everything from a line of input to a list of objects.
Questions instead of loops
The methods worth learning first are the ones that replace a whole loop with a sentence. map transforms every item and returns the new list; select keeps the items a condition likes and reject keeps the rest; sum, min and max answer directly; sort orders a copy; uniq removes duplicates; include? asks whether something is in there at all.
Each of them takes a block — the piece of code between the braces — and hands it one item at a time. The pattern to notice now is how often a loop becomes one of these calls: a loop that starts by building an empty array and pushing into it is almost always a map in disguise, and the disguise does not read as well.
Copies, and the bang methods
sort and map return new arrays and leave the original alone. Their relatives with a bang — sort!, map!, uniq! — change the array in place. The convention is that a bang method is the dangerous version, and the habit to form is to prefer the safe one and reach for the bang when you have decided that the old value is genuinely finished.
each_with_index is the bridge between the old loop and the new style: it walks the array the way each does but hands the block a second argument, the position, which is what you need when you are numbering lines rather than transforming them.
Positions, copies and blocks
An array is an ordered list with integer positions, and most of its methods are questions asked of the whole list. map, select and sort each build a new array; each walks the original for its side effects. The distinction is the one to keep straight: if you want the answers, use the method that returns them; if you want to print, each is enough.
Blocks make those methods flexible because the block decides the meaning. map asks what each item should become, select asks whether to keep it, and sort_by asks what it is worth for ordering. sum accepts a block too, which is how a list of records becomes one number without building an intermediate array.
The block decides the answer
The same array answers different questions depending on the block. map asks what each item becomes, select asks whether to keep it, sort_by asks what it is worth for ordering, and sum asks what to add. The method supplies the walking; the block supplies the meaning.
That division is why these methods compose. select then map reads as two decisions in order, and neither has to know about the other. A hand-written loop that does both in one pass is faster and harder to change, which is usually the wrong trade in a program someone will read.
An index is not a count
The last index of an array is one less than its length, because the first position is zero. scores[scores.length - 1] and scores[-1] name the same item; scores[scores.length] is one past the end and answers nil. When a loop runs one step too far, this arithmetic is where to look.
Three array mistakes repay attention. sort! and map! change the array that every other name is pointing at, so a caller's list can change under them. delete removes every matching value, while delete_at removes one position — different jobs with similar names. And an index past the end returns nil rather than raising, so scores[10] quietly poisons arithmetic three lines later; fetch(10) raises instead.
Lists, and the questions asked of them
scores = [72, 95, 60, 88]
puts scores.length
puts scores.first
puts scores.last
puts scores[-1]
p scores[1..2]
scores << 100
p scores
p scores.map { |s| s / 2 }
p scores.select { |s| s >= 70 }
p scores.reject { |s| s >= 70 }
puts scores.sum
puts scores.min
puts scores.max
p scores.sort
p scores.uniq
scores.each_with_index do |score, index|
puts "#{index}: #{score}"
end
# sort makes a copy; sort! changes the array itself.
ordered = scores.sort
p ordered
p scoresA week of readings
Run it and read each transformation. Then add a method call that finds the highest reading, and one that reports how many were above 20.
A method that ends in a question mark returns true or false — even?, include?, empty?. It is a naming convention rather than syntax, and it is honoured everywhere in Ruby, which is why scores.empty? reads as the question it answers.
Try these next
- Take an array of prices, add ten percent to each with
map, and keep the original beside it. - Select the scores above the average, then count how many there are.
- Sort a list of names by length with
sort_by, and print the shortest first.
A restock sheet
Run it and read the two lists. Then add a sixth item and its count, and change the restock rule so anything below eight is topped up to twelve.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is scores[-1]?
