#---Array---
Arrays are one of the most commonly used data structures . Ruby provides powerful and versatile arrays for all your needs.
Mastering them means you can get a lot done out of them.
There are many ways to create arrays.
Create an empty array:
arr = []
An array of items of same type:
nums = [11, 22, 33]
An array of arrays:
matrix_2_by_2 = [[1, 11], [2, 22], [3, 33]]
## Now create a 3 by 3 array. Go ahead. Make my day.
A nested array:
nested = [11, :a, [:alpha, :beta, [:matt, :joe]]]
An array of items of different types:
junkyard = [11, 'alpha', ['x', 'y'], nil, Object.new]
Creating with the Array.new constructor (rarely used):
p Array.new(3) # => [nil, nil, nil]
p Array.new(3, 'a') # => ["a", "a", "a"]
Using a block to dynamically create items:
p Array.new(3) { |i| i * 2 } # => [0, 2, 4]
If you don’t like quoting strings, use %w() to create
string arrays and %i() to create symbol arrays:
p %w(one two three) # => ["one", "two", "three"]
p %i(one two three) # => [:one, :two, :three]
The operator method [] is used to access one or more
items from the array:
arr = ['a', 'b', 'c', 'd', 'e']
Array index starts from 0:
p arr[0] # => "a" (first item)
p arr[4] # => "e" (last item)
To count backwards (from last to first), use negative indices:
p arr[-1] # => "e" (last item)
p arr[-2] # => "d" (second-to-last item)
Extract a portion of an array:
p arr[1, 3] # => ["b", "c", "d"]
Same thing with a range:
p arr[1..3] # => ["b", "c", "d"]
p arr[1...3] # => ["b", "c"]
There are other ways to access items too:
p arr.first # => "a"
p arr.last # => "e"
p arr.first(2) # => ["a", "b"]
p arr.last(2) # => ["d", "e"]
p arr.take(2) # => ["a", "b"]
Accessing beyond range returns nil. It doesn’t throw
any error:
p arr[5] # => nil
In some situations, this behavior can result in
unexpected bugs. If you want to know if the index is
out of range, use fetch instead of [].
This will blow up with an exception if the index is out of range:
p arr.fetch(5) rescue nil
## 💥 without `rescue nil` blows up with IndexError
You can provide a default value with fetch if there’s nothing in that index:
p arr.fetch(5, 'FIVE!') # => "FIVE!"
If you have a nested array, and you want to access a
specific item, you can use [] like so:
arr = [11, :a, [:alpha, :beta, [:matt, :joe]]]
p arr[2][2][1] # => :joe
But a better, safer way is to use dig. Why?
Assume arr is user input and you expect it to
have 3 items, with the third one being another array
whose third item is yet another array of 2 items.
But what if the user only supplied 2 items for arr?
What if the 3rd item is not present, or is nil? How
would the code above work?
It’ll blow up with a NoMethodError exception:
arr[2] = nil
p arr[2][2][1] rescue nil
## 💥 without `rescue nil` blows up with:
## => undefined method '[]' for nil (NoMethodError)
But with dig, you get nil if there’s no item in any
of the intermediate indices:
p arr.dig(2, 2, 1) # => nil
And you get the correct value if the item exists:
arr[2] = [:alpha, :beta, [:matt, :joe]]
p arr.dig(2, 2, 1) # => :joe
This means you can use this in conditionals.
Prior to dig, people had to do this:
if arr[2] && arr[2][2] && arr[2][2][1]
## do something
else
## inform user that input format is incorrect
end
Now you can simply do this. Lucky you:
if arr.dig(2, 2, 1)
else
end
Note: dig also works with arrays containing hashes,
that in turn contain arrays, and so on:
arr = [11, :a, [:alpha, :beta, {matt: [22, 33]}]]
p arr.dig(2, 2, :matt, 1) # => 33
Note^2: This is okay, but not cool. Pattern matching is cool:
arr => [_, _, [_, _, {matt: [_, age_of_matt]}]]
p age_of_matt # => 33
arr = [11, 22]
<< or push to append an item to the array’s
end:arr << 33
arr.push(44)
p arr # => [11, 22, 33, 44]
unshift to prepend an item to the array’s
beginning:arr.unshift(55)
p arr # => [55, 11, 22, 33, 44]
insert to place an item at any given index:arr.insert(3, 7)
p arr # => [55, 11, 22, 7, 33, 44]
concat to add arrays of items:arr.concat([:a, :b], [:c])
p arr # => [55, 11, 22, 7, 33, 44, :a, :b, :c]
Note: All these methods modify the original array, and also return the modified array as the return value.
pop to remote the last item from the array,
and also return that last item:popped = arr.pop
p popped # => 55
p arr # => [66, 11, 22, 7, 33, 44]
delete_at to delete an item at a specified
index:deleted = arr.delete_at(3)
p deleted # => 7
p arr # => [66, 11, 22, 33, 44]
delete simply:10.times { arr.push(44) } # check arr's value
arr.delete(44)
p arr # => [66, 11, 22, 33]
Use the []= operator method to replace an item in a
given index:
arr[2] = 222
p arr # => [66, 11, 222, 33]
You can replace consecutive items if you pass two
indices, or a range to []=:
arr[1, 2] = [44, 55]
p arr # => => [66, 44, 55, 33]
arr[1..2] = [66, 77]
p arr # => => [66, 66, 77, 33]
The simplest way to loop over an array’s
items is the each method:
arr = [11, 22, 33]
arr.each { |e| p e }
But it’s common to use many of the Enumerable methods
(seen below) to iterate over an array because, besides
looping, they also do some additional things.
size gives you the count of array items:p junkyard.size # => 5
empty? returns true if array has no items:p junkyard.empty?, [].empty? # => [false, true]
map takes a block and returns a new array of
items that’s the result of calling the block for each
item:p [1, 2, 3, 4].map { |e| e * 2 } # => [2, 4, 6, 8]
compact removes all nils from an array:p [11, 22, nil, 33, nil].compact # => [11, 22, 33]
flatten returns a single-dimensional array from all
the items flattened in a multi-dimensional array:p [1, 2, [3, 4], [[5, [6]]]].flatten
## => [1, 2, 3, 4, 5, 6]
uniq removes duplicates:p [11, 22, 11].uniq # => [11, 22]
sort does its thing:p [22, 11].sort # => [11, 22]
join joins the items with the given string:p %w(cat mat bat).join # => "catmatbat"
p %w(cat mat bat).join(',') # => "cat,mat,bat"
sample returns one or more random items:p arr.sample # => "a"
p arr.sample(3) # => ["d", "c", "b"]
shuffle returns a new array with items shuffled:p arr.shuffle # => ["e", "a", "c", "b", "d"]
include? returns true if the given item is
included in the array:p %w(11 22 33).include?(22) # => false
p %w(11 22 33).include?('22') # => true
intersect? is like include?, but it
can check an array of items instead of just one:p %w(11 22 33).intersect?(%w(1 2 3)) # => false
p %w(11 22 33).intersect?(%w(1 33 3)) # => true
All the methods above don’t modify the array they were called on. They just return a new modified array.
arr = [11, nil, 'a', 11, ['x', 'y']]
p arr.compact # => [11, "a", 11, ["x", "y"]]
p arr.uniq # => [11, nil, "a", ["x", "y"]]
p arr.flatten # => [11, nil, "a", 11, "x", "y"]
## `arr` hasn't changed one bit!
p arr
## => [11, nil, 'a', 11, ['x', 'y']]
To modify the original array, use the “bang”
versions of these methods: compact!, flatten!,
uniq!, sort!, reverse! etc.
arr.compact! # => [11, "a", 11, ["x", "y"]]
arr.uniq! # => [11, "a", ["x", "y"]]
arr.flatten! # => [11, "a", "x", "y"]
## Now `arr` is a changed man.
p arr
## => [11, "a", "x", "y"]
In general, prefer non-bang methods because they’re easier to debug as they don’t modify the original array.
a = [11, 22]
b = [22, 33]
c = [33, 44]
p (a + b + c) # => [11, 22, 22, 33, 33, 44]
union or | to return combined items from
all given arrays:p a.union(b, c) # => [11, 22, 33, 44]
p (a | b | c) # => [11, 22, 33, 44]
intersection or & to return common items from
all given arrays:p a.intersection(b) # => [22]
p a.intersection(b, c) # => []
p (a & b) # => [22]
p (a & b & c) # => []
difference or - to return items that are
not common among all given arrays:p a.difference(b, c) # => [11]
p (a - b - c) # => [11]
Note: union, intersection difference all can
take any number of arrays as arguments, whereas the |,
& and - operator methods can only take one array.
The Array class includes the Enumerable module. That’s
where it gets a lot of its superpowers from.
p Array.ancestors.include?(Enumerable) # => true
In fact, the compact, sort, map and uniq methods
mentioned above… they all come from Enumerable.
There’s a lot more. Read the docs for full list. But I’ll list some of my favorite and most useful methods:
find returns the first item that makes the
block return a truthy value:p [11, 22, 33].find { |e| e.odd? } # => 11
find_all returns an array of all items that make the
block return a truthy value:p [11, 22, 33].find_all { |e| e.odd? } # => [11, 33]
all? returns true only if all items make the block
return true:p [2, 4, 6].all? { |e| e.even? } # => true
any? returns true if even one item makes the block
return true:p [2, 4, 6].any? { |e| e.odd? } # => false
none? does the opposite. Returns true only if none
of the items make the block return true:p [2, 4, 6].none? { |e| e.odd? } # => true
grep? is a fun method. It returns only items that
match a given pattern. But it can also grep items
within a range, or items that are only of a particular
class:p %w(duck buck nick).grep(/uck$/) # => ["duck", "buck"]
p [11, '22', 33].grep(Integer) # => [11, 33]
p [11, 22, 33].grep(20..30) # => [22]
each_with_index is there just so you don’t have to
do this: i = 0 and then i += 1 for each iteration:[11, 22].each_with_index { |e, i| p "idx of #{e}: #{i}" }
## => "idx of 11: 0"
## => "idx of 22: 1"
each_with_object is elegant. It saves you an extra
step when you’re building an object based on each array
item:hash_obj = {}
%w(a b c).each_with_object(hash_obj) do |ele, hash_obj|
hash_obj[ele.to_sym] = ele.upcase
end
p hash_obj # => {a: "A", b: "B", c: "C"}
reduce is used when you want to reduce a whole array
into a single value based on a starting value, a
function and the individual array items one at a time:## sum of all items
p [10, 20, 30].reduce { |mem, e| mem + e } # => 60
## product of all items
p [10, 20, 30].reduce { |mem, e| mem * e } # => 6000
## 100, plus sum of all items
p [10, 20, 30].reduce(100) { |mem, e| mem + e } # => 160
In reduce, mem is just a block variable and it can
be named anything. But it’s customary to name it mem
or memo with reduce because it represents
“memory”. It is the value Ruby “keeps in memory”
during each iteration.
Many methods we discussed above have other names as well. These are method aliases.
map is the same as collectfind is detectfind_all is filter. It’s also selectreduce is injectinclude? is member?reduce is inject[] is sliceWhich one to use?
As with all things in Ruby related to style… it’s a
matter of preference.
(I prefer the left ones in the list above.)
to_h and Hash[] convert an array of arrays to a
hash:arr = [[:a, 1], [:b, 2]]
p arr.to_h # => {a: 1, b: 2}
p Hash[arr] # => {a: 1, b: 2}
to_json converts an array to a JSON string. But you
need to require json first in some older versions of
Ruby:require 'json'
puts arr.to_json # => [["a",1],["b",2]]
itFrom Ruby 3.4, instead of this:
p [11, 22].map { |e| e.even? }
You can do this:
p [11, 22].map { it.even? } # => [false, true]
But be careful. If there’s already an it variable in
the scope of the block’s definition, then only that will
be used inside the block. This is called closure.
it = 10
p [11, 22].map { it.even? } # => [true, true]
So, only use this feature:
it variable
defined already