Arrays
This chapter introduces Arrays, the most commonly used data structure in Ruby. An Array allows storing multiple values in a single variable.
Principle
So far, each variable held only a single value: a name, a level, a type. But how do you store a team of 6 Pokemon? Creating 6 separate variables (pokemon_1, pokemon_2...) would be tedious. That is the role of the Array: an ordered list of elements, accessible by their position.
You can think of an Array as a locker with several numbered compartments. Each compartment holds a value, and you access each value by its number.
Creating an Array
The simplest way to create an Array is to use square brackets []:
team = ['Pikachu', 'Charizard', 'Blastoise']
types = [:electric, :fire, :water]
levels = [25, 36, 40]
empty = []
p team # => ["Pikachu", "Charizard", "Blastoise"]
p empty # => []
- An Array can contain any type of value: Strings, Integers, Symbols, and even a mix of different types.
[]creates an empty Array.
You can also create an Array of a given size with Array.new:
# Create an Array of 6 slots, all filled with nil
slots = Array.new(6, nil)
p slots # => [nil, nil, nil, nil, nil, nil]
# Create an Array of 6 slots with computed values
levels = Array.new(6) { |index| (index + 1) * 10 }
p levels # => [10, 20, 30, 40, 50, 60]
Array.new(6, nil)creates 6 slots filled withnil.- The second form uses a block
{ |index| ... }that computes the value of each slot. We have not yet seen blocks in detail, but the syntax is simple: between{ }, the|index|receives the slot number (starting from 0), and the expression after produces the value.
Accessing elements
Each element has an index (its position in the Array). Note: indexes start at 0, not 1:
team = ['Pikachu', 'Charizard', 'Blastoise', 'Gengar', 'Dragonite', 'Snorlax']
# index 0 index 1 index 2 index 3 index 4 index 5
puts team[0] # => Pikachu (first element)
puts team[2] # => Blastoise (third element)
puts team[5] # => Snorlax (last element)
puts team[-1] # => Snorlax (last, counting from the end)
puts team[-2] # => Dragonite (second to last)
team[0]accesses the first element. This is a programming convention: counting starts from 0.- Negative indexes count from the end.
-1is the last,-2the second to last.
Ruby also offers shortcuts:
team = ['Pikachu', 'Charizard', 'Blastoise']
puts team.first # => Pikachu
puts team.last # => Blastoise
Modifying an element
You can replace an element by assigning a new value to an index:
team = ['Pikachu', 'Charizard', 'Blastoise']
puts team[0] # => Pikachu
team[0] = 'Raichu'
puts team[0] # => Raichu
p team # => ["Raichu", "Charizard", "Blastoise"]
Getting the size
team = ['Pikachu', 'Charizard', 'Blastoise']
puts team.size # => 3
puts team.length # => 3 (identical to .size)
puts team.empty? # => false
puts [].empty? # => true
.sizeand.lengthdo the same thing..empty?returnstrueif the Array contains no elements.
Adding elements
team = ['Pikachu']
# Add to the end
team.push('Charizard')
team << 'Blastoise' # shortcut, the most commonly used
p team # => ["Pikachu", "Charizard", "Blastoise"]
# Add to the beginning
team.unshift('Mew')
p team # => ["Mew", "Pikachu", "Charizard", "Blastoise"]
# Insert at a specific position
team.insert(2, 'Gengar')
p team # => ["Mew", "Pikachu", "Gengar", "Charizard", "Blastoise"]
<<is the most common way to add to the end. It is read as "append" or "push"..unshiftadds to the beginning..insert(position, value)inserts at a given index.
Removing elements
team = ['Pikachu', 'Charizard', 'Blastoise', 'Gengar']
# Remove the last
last_removed = team.pop
puts last_removed # => Gengar
p team # => ["Pikachu", "Charizard", "Blastoise"]
# Remove the first
first_removed = team.shift
puts first_removed # => Pikachu
p team # => ["Charizard", "Blastoise"]
# Remove by value (all occurrences)
team = ['Pikachu', 'Charizard', 'Pikachu']
team.delete('Pikachu')
p team # => ["Charizard"]
# Remove by index
team = ['Pikachu', 'Charizard', 'Blastoise']
team.delete_at(1)
p team # => ["Pikachu", "Blastoise"]
.popand.shiftreturn the removed element, allowing you to store it in a variable..delete(value)removes all occurrences of that value.
Searching in an Array
team = ['Pikachu', 'Charizard', 'Blastoise', 'Gengar']
puts team.include?('Pikachu') # => true
puts team.include?('Mewtwo') # => false
puts team.index('Blastoise') # => 2 (its position)
puts team.index('Mewtwo') # => nil (not found)
.include?checks whether a value is in the Array..indexreturns its position, ornilif not found.
Iterating over an Array with each
To do something with each element of an Array, use .each:
team = ['Pikachu', 'Charizard', 'Blastoise']
team.each { |pokemon| puts "Go, #{pokemon} !" }
Displays:
Go, Pikachu !
Go, Charizard !
Go, Blastoise !
.eachpasses each element, one by one, to the block{ |pokemon| ... }. The wordpokemonbetween the pipes| |is the name given to the current element. You can choose any name, but it is more readable to use the singular of the collection.- In Ruby, many Array methods accept a block to customize their behavior. In this chapter, some methods are shown without a block (like
.sort,.reverse), but they often have a block variant that we will discover later. When you see an Array method, there is a good chance you can pass it a block. - For a block that fits on one line, use
{ }. For a longer block, usedo ... end:
team.each do |pokemon|
puts "Pokemon: #{pokemon}"
puts " Name length: #{pokemon.length}"
end
Iterating with the index
When you need the position of each element along with its value:
team = ['Pikachu', 'Charizard', 'Blastoise']
team.each_with_index do |pokemon, index|
puts "#{index + 1}. #{pokemon}"
end
Displays:
1. Pikachu
2. Charizard
3. Blastoise
.each_with_indexpasses two values to the block: the element and its index. We add 1 to the index for a more natural display (starting at 1 rather than 0).
Transforming an Array with map
.map creates a new Array by transforming each element:
team = ['Pikachu', 'Charizard', 'Blastoise']
uppercased_team = team.map { |pokemon| pokemon.upcase }
p uppercased_team # => ["PIKACHU", "CHARIZARD", "BLASTOISE"]
p team # => ["Pikachu", "Charizard", "Blastoise"] (unchanged)
.mapdoes not modify the original Array. It returns a new Array with the results.
Filtering an Array with select and reject
team = ['Pikachu', 'Charizard', 'Blastoise', 'Gengar', 'Dragonite']
# Keep only names longer than 8 characters
long_names = team.select { |pokemon| pokemon.length > 8 }
p long_names # => ["Charizard", "Gengar", "Dragonite"]
# Exclude names longer than 8 characters
short_names = team.reject { |pokemon| pokemon.length > 8 }
p short_names # => ["Pikachu", "Blastoise"]
.selectkeeps elements for which the block returnstrue..rejectdoes the opposite: it excludes those for which the block returnstrue.
Sorting and reordering
team = ['Blastoise', 'Pikachu', 'Charizard']
p team.sort # => ["Charizard", "Pikachu", "Blastoise"] (alphabetical order)
p team.reverse # => ["Charizard", "Pikachu", "Blastoise"] (reversed order)
levels = [36, 25, 40, 10]
p levels.sort # => [10, 25, 36, 40] (ascending order)
.sortsorts in alphabetical order (String) or numerical order (Integer)..reversereverses the order..sortcan also take a block to sort by a custom criterion (for example, sorting by name length). We will see this form in later chapters.- Like
.mapand.select, these methods return a new Array without modifying the original.
Other useful methods
# Remove nils
sparse = ['Pikachu', nil, 'Blastoise', nil]
p sparse.compact # => ["Pikachu", "Blastoise"]
# Remove duplicates
duplicates = ['Pikachu', 'Charizard', 'Pikachu', 'Blastoise']
p duplicates.uniq # => ["Pikachu", "Charizard", "Blastoise"]
# Convert an Array to a String
team = ['Pikachu', 'Charizard', 'Blastoise']
puts team.join(', ') # => Pikachu, Charizard, Blastoise
puts team.join(' | ') # => Pikachu | Charizard | Blastoise
.compactremoves allnilvalues..uniqremoves duplicates..joinis the inverse of.split(seen in chapter 2): it combines elements into a single String with a separator.
Conclusion
- An Array is an ordered list of elements, accessible by index (which starts at 0).
<<is the idiomatic way to add an element to the end..eachiterates over each element..each_with_indexalso gives the position..maptransforms each element and returns a new Array..selectkeeps elements matching a condition..rejectdoes the opposite..sortsorts,.reversereverses,.compactremoves nils,.uniqremoves duplicates..include?checks for the presence of an element..indexreturns its position..joinconverts to a String..split(seen in chapter 2) does the reverse.