В качестве интересной задачи давайте посмотрим, как получить общую численность населения всех стран вместе взятых. Мы можем использовать метод значений, чтобы получить массив счетчиков населения, а затем вызвать метод суммы для этого массива, чтобы агрегировать его:
puts "Total population: #{population.values.sum}"
Если вы попробуете этот код, вы увидите, что он не работает со следующим сообщением об ошибке:
Unhandled exception: Arithmetic overflow (OverflowError)
Проблема в том, что популяции — это экземпляр Hash(String, Int32)
, и поэтому вызов значений в нем приведет к созданию экземпляра Array(Int32)
. Если сложить эти значения, получится 4 503 002 371
, но давайте напомним себе, что экземпляр Int32
может представлять только целые числа от -2 147 483 648
до 2 147 483 647
.
Результат выходит за пределы этого диапазона и не помещается в экземпляр Int32
. В этих случаях Crystal не выполнит операцию вместо автоматического повышения целочисленного типа или предоставления неверных результатов.
Одним из решений было бы с самого начала хранить счетчики населения как Int64
, указав тип, как если бы мы делали это с пустым хешем:
population = {
"China" => 1_439_323_776,
"India" => 1_380_004_385,
# ...
"Mexico" => 128_932_753,
} of String => Int64
Другое решение — передать начальное значение методу суммы, используя правильный тип:
puts "Total population: #{population.values.sum(0_i64)}"
Теперь давайте посмотрим, как мы можем перебирать эти коллекции.
Итерация коллекций с блоками
При вызове метода можно передать блок кода, разделенный do...end
. Несколько методов получают блок и работают с ним, многие из них позволяют каким-либо образом выполнять циклы. Первый пример — метод цикла. Это просто — он просто зацикливается навсегда, вызывая переданный блок:
loop do
puts "I execute forever"
end
Это прямой эквивалент использования while true
:
while true
puts "I execute forever"
end
Два других очень полезных метода, которые берут блоки, — это times
и each
. Вызов times
для целого числа приведет к повторению блока указанное количество раз, а вызов каждого из коллекции вызовет блок для каждого элемента:
5.times do
puts "Hello!"
end
(10..15).each do |x|
puts "My number is #{x}"
end
["apple", "orange", "banana"].each do |fruit|
puts "Don't forget to buy some #{fruit}s!"
end
В предыдущем примере показано, как можно использовать блоки для обхода некоторой коллекции. При написании кода Crystal это предпочтительнее, чем итерация с помощью цикла while
. Несколько методов из стандартной библиотеки принимают блок: мы видели каждый, но есть также карта для преобразования каждого элемента во что-то другое, выбора или отклонения для фильтрации элементов на основе некоторого условия и сокращения для вычисления значения на основе каждого элемента.
Синтаксис короткого блока
Очень частым случаем является вызов метода, передающего блок, имеющий только один аргумент, а затем вызов метода для этого аргумента. Например, предположим, что у нас есть массив строк, и мы хотим преобразовать их все в заглавные буквы. Вот три способа написать это:
fruits = ["apple", "orange", "banana"]
# (1) Prints ["APPLE", "ORANGE", "BANANA"]
p(fruits.map do |fruit| fruit.upcase
end)
# (2) Same result, braces syntax
p fruits.map { |fruit| fruit.upcase }