In this article, I'll focus on Ruby. I'll use the version 2.5.1:
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu]
Basic Ruby arrays
Like my previous article on Python here Python lists, I'll focus on some more advanced array features rather than basic ones.
digits = [0,1,2,3,4,5,6,7,8,9]
a = ["one", "two", "three", "four"]
Creating an array in Ruby is as easy as in Python:
digits = [0,1,2,3,4,5,6,7,8,9]
a = ["one", "two", "three", "four"]
# empty araay
empty = []
empty = Array.new()
empty = Array.new
You can mix types:
mixed_array = ["one", 2, "three", 4]
When dealing with strings, you can use the %w() construct to build an array from a litteral string:
lipsum = %w(Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.)
or even create an array of arrays:
binomial_coefficients = [
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1],
[1,5,10,10,5,1]
]
To initialize an array with the same element, just use * :
['A'] * 5 # gives ['A', 'A', 'A', 'A', 'A']
# or
Array.new(5,'A')
The number of elements of an array is given by length() method:
binomial_coefficients.length # gives 6
lipsum.length # 19
You can also store objects, classes or functions in an array:
# array of functions
trigo = [Math.method(:sin),Math.method(:cos),Math.method(:tan)]
# array of lambdas
powers = [
->(x) { x*x },
->(x) { x**3 },
->(x) { x**4 }
]
# array of classes
require 'set'
collections = [Array, Hash, Set]
# array of objects
empty_collections = [Array.new, Hash.new, Set.new]
Accessing elements
Accessing array elements is business as usual, with some very useful refinements :
first_binomial = binomial_coefficients[0]
last_binomial = binomial_coefficients[-1]
fifth_binomial = binomial_coefficients[-2]
binomial_coefficients[4].length # gives 5
Sub-arrays are possible using index slices:
first3_binomials = binomial_coefficients[0...3]
first4_binomials = binomial_coefficients[0..3]
Note the difference between the open range (...)
and the closed one (..)
.
Array operations
- adding an element
digits.push(10)
- deleting an element by index
digits.delete_at(10)
- concatenating arrays
digits = [0,1,2,3,4] + [5,6] + [7,8,9]
- testing element membership
# methods ending with a trailing ? return a boolean
if digits.include?(9) then
puts("9 is a digit ! Such a surprise ;-)")
end
Looping through an array
Use the for-in construct (also available for until, while, until)
for d in digits do
puts(d)
end
but a more functional oriented way is to use the each method:
digits.each { |d| puts(d) }
To get the element index when looping, use the each_with_index method:
digits.each_with_index { |d,i|
puts("#{d} is the #{i}-th digit")
}
More advanced usage
Some useful functions on arrays
digits = [0,1,2,3,4,5,6,7,8,9]
digits.sum # gives 45
digits.max # gives 9
digits.min # gives 0
# get any word being the longest
lipsum.max{ |a, b| a.length <=> b.length } # gives 'consectetur'
The zip() built-in array method combines several arrays to create a resulting array, created by taking the i-th element of each array:
a = [0,1,2,3]
b = [4,5,6,7]
a.zip(b) # gives [[0, 4], [1, 5], [2, 6], [3, 7]]
No array comprehensions
In Ruby, there's no clean syntax as list comprehensions in Python. But those are very akin to higher order function as map. So you can achieve the same result with map or collect methods:
# extract words ending with 't'
# compact() method is used to get rid of nil values
lipsum = %w(Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.)
end_with_t = lipsum.map { |w| w if w.end_with?('t') }.compact # gives ['sit', 'incididunt', 'ut', 'et']
# convert to uppercase
lipsum.map { |w| w.upcase }
lipsum.map(&:upcase)
# get only words of length 5 (including commas)
lipsum.map { |w| w if w.length == 5 }.compact # gives ['Lorem', 'ipsum', 'dolor', 'amet,', 'elit,', 'magna']
# create new objects from an array of classes
require 'set'
[Array, Hash, Set].map{ |cls| cls.new }
# same as before when function called doesn't require an argument
[Array, Hash, Set].map(&:new)
# get pi/4 value from trigonometric functions array
trigo = [Math.method(:sin),Math.method(:cos),Math.method(:tan)]
trigo.map { |f| f.call(Math::PI/4) }
Using the to_a method on an enumerable
Similar to the built-in list() function in Python, Ruby comes with the to_a method which creates an array from an enumerable (Ruby name for iterator):
# this creates an array of a-z chars
a_to_z = ('a'..'z').to_a
# create digits and the first 100 even numbers
digits = (0..9).to_a
even = (0...100).step(2).to_a
# to_a() is idempotent, but this creates another reference not a new array
digits.to_a # gives back a copy of digits
This obviously works for user defined iterators:
class One
include Enumerable
def each
yield "one"
yield "un"
yield "ein"
yield "uno"
end
end
# gives ['one', 'un', 'ein', 'uno']
One.new.to_a
Inheriting the Array class
Nothing prevents you to subclass the Array class to create your own user-defined array. This example implements a way to access elements of an array by giving several indexes:
# Based on the Array class, but accept sparse indexes
# Doesn't manage range of chars
class MyArray < Array
def [](*index)
if index.length == 1 &&
(index[0].class == Range || index[0].class == Integer)
super(index[0])
else
index.collect { |i| super(i) }
end
end
end
a = MyArray.new(('a'..'z').to_a)
a[1] # "b"
a[0..3] # ["a", "b", "c", "d"]
a[0...3] # ["a", "b", "c"]
a[0,24,25] # ["a", "y", "z"]
Acting on arrays
Using functional programming built-in functions, you can extract values from an array, or get another array from the source one.
map() or collect()
Using the map() built-in function, it's possible to get an image of a mapping on the array. If you consider an array as a mathematical set of elements, map() gives the image set through the considered function.
a_to_z = ('a'..'z').to_a
A_to_Z = a_to_z.map{ |c| c.upcase }
# map() uses a block which can be more advanced
# greek letters: ["α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "ι", "κ", "λ", "μ", "ν", "ξ", "ο", "π", "ρ", "ς", "σ", "τ", "υ", "φ", "χ", "ψ", "ω", "ϊ", "ϋ", "ό", "ύ", "ώ"]
greek = ('α'..'ω').to_a
greek.map{ |g|
case g
when "α"
"A"
when "β"
"B"
when "γ"
"C"
# and so on
else
"X"
end
}
Of course, the map function to pass as the first argument could be any function, and any lambda having one argument is possible:
digits = (0..9).to_a
digits.map( &->(x) { x*10 } ) # gives tenths
# refer to binomial_coefficients above
binomial_coefficients.map(&:sum) # gives [1, 2, 4, 8, 16, 32]
or even a user-defined function:
# contrived example
def square(x)
x**2
end
# calculate first 9 perfect squares
digits = (0..9).to_a
digits.map(&method(:square)) # gives [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- select()
This built-in function is used to sieve elements from an array, using some criteria. Elements kept are those where the function given as the first argument to select() returns true.
# extract even numbers
digits = (0..9).to_a
digits.select { |n| n%2 == 0 } # gives [0, 2, 4, 6, 8]
# extract words less than 4 chars
lipsum = %w(Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.)
lipsum.select { |w| w.length < 4 } # gives ['sit', 'sed', 'do', 'ut', 'et']
- inject()
Refer to my previous article on Python's reduce() to get some details on the inject() method.
Examples:
digits = (0..9).to_a
# sum of first 10 digits
digits.inject { |x,y| x+y } # gives 45
# this uses an initializer
digits.inject(10) { |x,y| x+y } # gives 55 = 45+10
# a more sophisticated example: this uses the nested multiplication to compute the value of a polynomial, given its coefficients and the unknown value z. BTW, an example of how to extend the Array class ;-)
class Array
def nested(z)
self.inject { |x,y| z*x+y }
end
end
[1,5,10,10,5,1].nested(1) # gives 32
# easy computation of the nested square root which converges to the golden ratio
([1]*100).inject { |x,y| Math::sqrt(x+y) } # gives 1.618033988749895
# same for the canonical continued fraction. Note to to_f method to convert to float when dividing, otherwise no division is applied
([1]*100).inject { |x,y| y+1/x.to_f }
# this sums all columns of a matrix
matrix = (0..3).to_a.map { |i| [i]*4 }
matrix.each_with_index.inject([0]*4) { |x, (y,i)|
x.map!.with_index { |e,i| e+y[i] }
}
Last part will be devoted to Rust vectors.
Photo by Susan Yin on Unsplash
Top comments (0)