Проблема в том, что популяции — это экземпляр 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 }
# (3) Same result, short block syntax
p fruits.map &.upcase
В первом фрагменте (1) использовался метод карты вместе с блоком do... end
. Метод map
выполняет итерацию по массиву, передавая блок для каждого элемента и создавая новый массив с результатом блока. В этом первом примере необходимы круглые скобки, поскольку do...end
блоки подключаются к самому внешнему методу, в данном случае p
.
Второй фрагмент (2) использует синтаксис { ... }
и может опускать круглые скобки, поскольку этот блок подключается к ближайшему вызову метода. Обычно синтаксис { ... }
записывается в одну строку, но это не обязательно.
Наконец, мы видим синтаксис коротких блоков в третьем фрагменте (3). Написание &.foo
аналогично использованию { |x| x.foo }
. Его также можно записать как p fruits.map(&.upcase)
, как если бы блок был общим аргументом вызова метода.
Отличается только синтаксис; поведение и семантика всех трех фрагментов одинаковы. Обычно везде, где это возможно, используется синтаксис коротких блоков.
Контейнер Tuple
также отображается в определениях методов при использовании параметров splat
.
Параметры сплата (Splat)