Шрифт:
Интервал:
Закладка:
Другой способ выйти из выполнения блока — использовать ключевое слово break.
Использование break внутри блока
Используйте break, чтобы остановить метод, вызывающий блок, действуя так, как если бы он вернулся. Расширяя тот же пример, что и раньше, посмотрите на следующее:
result = generate do |x|
if x == 2
break 10 # break instead of next
end
x + 1
end
p result
В этом случае yield 1 будет равна 2, но yield 2 никогда не вернется; вместо этого метод generate будет сразу завершен, а result получит значение 10. Ключевое слово break приводит к завершению метода, вызывающего блок.
Возвращение изнутри блока
Наконец, давайте посмотрим, как ведет себя return при использовании внутри блока. Гипотеза Коллатца — это интересная математическая задача, которая предсказывает, что последовательность, в которой следующее значение вдвое превышает предыдущее, если оно четное, или в три раза больше плюс один, если оно нечетное, в конечном итоге всегда достигнет 1, независимо от того, какое начальное число выбрано.
Следующий метод collatz_sequence реализует эту последовательность, бесконечно вызывая блок для каждого элемента. Эта реализация не имеет условия остановки и может либо работать вечно, либо быть завершена раньше вызывающей стороной.
Затем следует реализация метода, который запускает collatz_sequence с некоторым начальным значением и подсчитывает, сколько шагов необходимо, чтобы достичь 1:
def collatz_sequence(n)
while true
n = if n.even?
n // 2
else
3 * n + 1
end
yield n
end
end
def sequence_length(initial)
length = 0
collatz_sequence(initial) do |x|
puts "Element: #{x}"
length += 1
if x == 1
return length # <= Note this 'return'
end
end
end
puts "Length starting from 14 is: #{sequence_length(14)}"
Метод sequence_length отслеживает количество шагов и, как только оно достигает 1, выполняет возврат. В этом случае обратите внимание, что возврат происходит внутри блока метода collatz_sequence. Ключевое слово return останавливает вызов блока (например, next), останавливает метод, который вызвал блок с yield (например, break), но затем также останавливает метод, в котором записывается блок. Напоминаем, что return всегда завершает выполнение определения, которое находится внутри.
В этом примере кода выводится Length starting from 14 is: 17. Фактически, гипотеза Коллатца утверждает, что этот код всегда найдет решение для любого положительного целого числа. Однако это нерешенная математическая проблема.
Контейнеры данных
Crystal имеет множество встроенных контейнеров данных, которые помогут вам манипулировать и организовывать нетривиальную информацию. Наиболее распространенным на сегодняшний день является массив. Вот краткий обзор наиболее часто используемых контейнеров данных в Crystal:
• Array (Массив) — линейный и изменяемый список элементов. Все значения будут иметь один тип, возможно, объединение.
• Tuple (Кортеж) — линейный и неизменяемый список элементов, в котором точный тип каждого элемента сохраняется и известен во время компиляции.
• Set (Набор) — уникальная и неупорядоченная группа элементов. Значения никогда не повторяются, и при перечислении значения отображаются в том порядке, в котором они были вставлены (без дубликатов).
• Hash (Хэш) — уникальная коллекция пар ключ-значение. Значения можно получить по их ключам и перезаписать, обеспечивая уникальность ключей. Как и Set, он нумеруется в порядке вставки.
• NamedTuple — неизменяемая коллекция пар ключ-значение, где каждый ключ известен во время компиляции, а также тип каждого значения.
• Deque — изменяемый и упорядоченный список элементов, предназначенный для использования либо в виде структуры стека (FIFO, или First In First Out), либо в качестве структуры очереди (FILO, или First In Last Out). Он оптимизирован для быстрой вставки и удаления на обоих концах.
Далее давайте подробнее рассмотрим некоторые из этих типов контейнеров.
Массивы и кортежи
Вы можете выразить некоторые простые данные с помощью чисел и текста, но вам быстро понадобится собрать больше информации в списки. Для этого вы можете использовать массивы и кортежи. Массив — это динамический контейнер, который может увеличиваться, сжиматься и изменяться во время выполнения программы. С другой стороны, кортеж статичен и неизменяем; его размер и типы элементов известны и фиксируются во время компиляции:
numbers = [1, 2, 3, 4] # This is of type Array(Int32)
numbers << 10
puts "The #{numbers.size} numbers are #{numbers}"
# => The 5 numbers are [1, 2, 3, 4, 10]
С массивами нельзя смешивать разные типы, если они не были указаны при создании массива. Эти ошибки обнаруживаются во время сборки; они не являются исключениями во время выполнения. Посмотрите это, например:
numbers << "oops"
# Error: no overload matches 'Array(Int32)#<<' with type String
Используя типы объединения, вы можете иметь массивы, в которых сочетаются более одного типа, либо инициализируя их несколькими типами, либо явно указывая их. Вот пример:
first_list = [1, 2, 3, "abc", 40]
p typeof(first_list) # => Array(Int32 | String)
first_list << "hey!" # Ok
# Now all elements are unions:
element = first_list[0]
p element # => 1
p element.class # => Int32
p typeof(element) # => Int32 | String
# Types can also be explicit:
second_list = [1, 2, 3, 4] of Int32 | String
p typeof(second_list) # => Array(Int32 | String)
second_list << "hey!" # Ok
# When declaring an empty array, an explicit type is mandatory:
empty_list = [] of Int32
Внутри массива все значения имеют один и тот же тип; значения разных типов при необходимости расширяются до объединения типов или общего предка. Это важно, поскольку массивы изменяемы, и значение по заданному индексу можно свободно заменить чем-то другим.
Тип Array реализует