In programming, iteration refers to the process of repeating a block of code multiple times. This can be done by using concepts such as while loops and until loops. It can also be done by using recursion, which is a function that calls itself.
However, most often you want to iterate over a collection of items, such as an Array
, Range
or String
.
Both the Array
and Range
classes in Crystal include the Enumerable
module, which provides a number of methods for iterating over the elements.
Meanwhile, the String
class has its own set of methods for iterating over the characters.
Crystal also doesn't have any for statement like other languages. Instead it has several methods that can be used to iterate.
The most common way to iterate over a collection is to use the each
method, which yields each element in the collection to a block.
This can be done easily with a Range
.
Say you want to loop between 1 and 3, you can use the each
method to iterate over the range.
(1..3).each do |n|
puts n
end
# Output:
# 1
# 2
# 3
Even simpler, if you just want to iterate a number of times you can use the times
method, which exists on the Int
class.
3.times do |n|
puts n
end
# Output:
# 0
# 1
# 2
String
A String
is a sequence of characters and doesn't belong to the Enumerable
module, which means it has its own set of methods for iterating over the characters.
The most common way to iterate over a String
is to use the each_char
method, which yields each character in the String
to a block.
The each_char
method feeds a Char
object and not a String
object to the block.
str = "hello"
str.each_char { |char| puts char }
# Output:
# h
# e
# l
# l
# o
Another way of iterating over a String
is to use the each_line
method.
This method is mostly used when reading a file line by line.
str = "hello\nworld"
str.each_line do |line|
puts line
end
# Output:
# hello
# world
Enumerable
moduleThe Enumerable
module provides a number of methods for iterating over the elements of a collection.
Collections that include the Enumerable
module are Array
, Range
, Hash
, Set
, and others, the later ones will be covered in later concepts.
The most common way to iterate over an Array
is to use the each
method, which yields each element in the Array
to a block.
arr = [5, 2, 3]
arr.each do |element|
puts element
end
# Output:
# 5
# 2
# 3
The map
method is another way to iterate over an Array
, it returns a new Array
containing the results of applying the block to each element.
It is similar to the each
method, but it returns a new Array
with the transformed elements.
Transformation is very useful when you want to apply a function to each element of the collection, which is a common operation.
arr = [1, 2, 3]
new_arr = arr.map do |element|
element * 2
end
new_arr
# => [2, 4, 6]
With a control flow such as if
so can you filter which elements to transform.
arr = [1, 2, 3]
new_arr = arr.map do |element|
if element.odd?
element * 2
else
element
end
end
new_arr
# => [2, 2, 6]
In Crystal, there is a shorthand syntax for iterating and applying a method to each element of a collection.
This is usefull when working with simple logic which only requires to be transformed with one method.
This is done by using the &.
syntax followed by the method name.
arr = [1, 2, 3]
arr.map(&.to_s)
# => ["1", "2", "3"]
Sometimes you need to know the index of the element you are iterating over, you can use the each_with_index
or map_with_index
method for that.
It yields each element and its index to a block, the method also accepts an optional argument to specify the starting index.
arr = [1, 2, 3]
arr.each_with_index(4) do |element, index|
puts "Element: #{element}, Index: #{index}"
end
# Output:
# Element: 1, Index: 4
# Element: 2, Index: 5
# Element: 3, Index: 6
Note that the each_char_with_index
method is also available for String
objects.
The sum
method returns the sum of all elements in the collection, it also accepts an optional block to transform the elements before summing them.
It also accepts an optional argument to specify the initial value of the sum.
arr = [1, 2, 3]
arr.sum
# => 6
arr.sum(2) { |n| n * 2 }
# => 14
reduce
or fold as it is known in other languages, is a method that with a combine method and an initial value, it will combine all the elements in the collection.
The reduce
method has an accumulator that is passed to the block, the accumulator is the result of the previous iteration.
This becomes a recursive process that will combine all the elements in the collection.
arr = [1, 2, 3]
arr.reduce(0) do |acc, n|
acc + n
end
# => 6
reduce
might seem similar to sum
, but reduce
is more flexible because it allows you to specify the initial value of the accumulator and the combine method.
reduce
can be used to implement sum
, count
, and other methods.