Выбрать главу

В качестве интересной задачи давайте посмотрим, как получить общую численность населения всех стран вместе взятых. Мы можем использовать метод значений, чтобы получить массив счетчиков населения, а затем вызвать метод суммы для этого массива, чтобы агрегировать его:

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 }