Циклы и массивы
При промышленной разработке нам зачастую необходимо обрабатывать большие наборы данных. Для решения соответствующих задач на Ruby необходимо разобраться как с базовыми структурами данных, массивами, так и со средствами выполненя кода несколько раз, то есть с циклами. Однако стоит отметить то, что в идеоматическом коде на Ruby почти не используются циклы - их эффективно замеяют итераторы и нумераторы.
Циклы
Циклы while
, until
Циклы позволяют выполнять некоторый блок кода в соответствии с условием. Цикл while
выполняется пока условие верно, цикл until
выполняется пока условие не является верым. Выведем числа от 0 до 10 с помощью цикла while
.
a = 0
while a < 10
puts a
a += 1
end
puts a
Выведем числа от 1 до 11 с помощью цикла until
.
a = 0
until a > 10
puts a
a += 1
end
puts a
Результатом выражения, описывающего цикл, является nil
за исключением случая, когда вы прерываете выполнение цикла с помощью ключевого слова break
.
Модификаторы unti
и while
Данные циклы, как и условные операторы if
и unless
можно использовать в формате модификаторов выражений. Выражение будет выполняться до тех пор пока условие верно или неверено.
a = 0
a += 1 while a < 10
puts a # Выводит 10
a = 0
a += 1 until a > 10
puts a # Выводит 11
С их помощью можно организовать выполнение блока кода хотя бы 1 раз перед проверкой условия. Для этого используем описание блока с помощью конструкции begin
-end
. Организуем вывод чисел от 0 до 10 с помощью такой формы цикла.
a = 0
begin
puts a
a += 1
end while a < 10
puts a
Цикл for
Данный цикл предназначен для прохождения по элементам массива. В настоящее время не используется, так как инструмент значительно слабее итераторов, мы не будем его рассматривать.
Бесконечный итератор loop
Зачастую мы не можем «красиво» описать условие выхода из цикла или их может быть несколько. В такой ситуации можно использовать бесконечный итератор loop
, который будет выполнять ассоциированный с ним блок бесконечно. Организуем вывод чисел от 0 до 10 с помощью такого цикла.
a = 0
loop do
puts a
a += 1
break if a > 10
end
Управляющие слова
Прерывание циклов с помощью break
Ключевое слово break
позволяет прервать выполнение текущего цикла, а также любого итератора. Вы можете также передать данные, которые станут результатом выражения-цикла. Для итераторов это скорее всего не сработает.
Цикл прервёт свою работу в том случае, если элемент массива values
является чётным.
values.each do |value|
break if value.even?
# ...
end
Вариант возвращения результата из итератора. Находим квадрат первого чётного числа в массиве.
result = [1, 2, 3].each do |value|
break value * 2 if value.even?
end
puts result # prints 4
Однако стоит знать, что если в массиве не было ли одного чётного числа, тогда результатом работы данного кода станет ссылка на массив, по которому мы выполняли итерацию. Если вам необходимо вернуть результат, то лучше воспользоваться соответствующим итератором вместо использования break
.
Переход к следующей итерации с помощью next
Вызов next
прерывает дальнейшее выполнение кода в текущей итерации и переходит к выполнению следующей итерации в цикле или итераторе. next
также как и break
позволяет определить результат текущей итерации. Это свойство в отличие от предыдущего может быть использовано в повседневном программировании.
Например, в итераторе map
мы создаём новый массив, который содержит в себе удвоенные значения для всех нечётных чисел. Чётные числа не изменяются.
result = [1, 2, 3].map do |value|
next value if value.even?
value * 2
end
print result # Выводит массив [2, 2, 6]
Массивы
Массивы в Ruby всегда являются динамическими, т.е. можно менять не только их содержимое, но и количество элементов, содержащихся в массиве. В результате этот класс является базовым блоком при реализации почти любого приложения.
Помимо, собственно, хранения информации класс предоставляет возможности по удобной обработке этих элементов. Вы можете:
- создавать новые массивы на основании оригинального, например получить квадраты всех чисел в массиве;
- считать некотору общую характеристику для всех элементов массива, напирмер их сумму;
- фильтровать элементы в соответствии с некоторым критерием, например выделять положительные числа.
Это всё возможно с помощью методов, которые встроены в класс массив, что зачастую уменьшает необходимость в том, чтобы использовать циклы для обработки данных.
Объявление массива
Для создания массива обычно используются следующие подходы:
- Создание пустого массива с помощью литерала
[]
. - Преобразование другого объекта в массив с помощью метода
#to_a
. Для различных классов этот метод рабоатет по-разному. - Использование специальных методов для создания массивов строк
%w()
и%W()
.%w(foo bar baz #{1+1}) == ["foo", "bar", "baz", "\#{1+1}"] %W(foo bar baz #{1+1}) == ["foo", "bar", "baz", "2"]
- Ипользование специальных методов для создания массивов симоволов
%i()
.%i(address city) == [:address :city]
Методы для изменения содержимого массива
Базовыми действиями, которые мы хотим выполнять с массивами: добавлять и удалять данные, а также получать доступ к ним. Для решения последней задачи удобно использовать метод #[]
, который позволяет получить доступ как к одному элементу, так и выборке. Для изменения элемента по его номеру можно использовать метод #[]=
.
Для добавления данных в массив существуют следующие методы:
#push
или#append
добавляет в конец массива 1 или несколько элементов.#unshift
или#prepend
добавляет 1 или несколько элементов в начало массива.#<<
добавляет ровно 1 элемент в конец массива.
Для удаления элемента из массива существуют следующие методы:
#pop
удаляет 1 или несколько элементов с конца массива и возвращает его в результате своего выполнения. Если удалено несколько элементов, то он вернёт новый массив.#shift
удаляет 1 или несколько элементов с начала массива. Если было удалено больше одного элемента, то метод вернёт массив.#delete_at
позволяет удалить 1 элемент по определённому адресу. Метод возвращает удалённый элемент.
Все рассмотренные тут методы изменяют текущий массив. Однако при выполнении сложных обработок зачастую удобнее создать один или несколько промежуточных массивов, содержащих результаты обработки данных. Конечно, такой подход не будет «оптимальным» с точки зрения эффективности использования ресурсов, однако он зачастую может оказаться более читаемым для программиста. А ведь программы в первую очередь пишутся для людей, а уже в следующую очередь для интерпретатора.
Итераторы
Итератором в Ruby называется метод, который вызывает блок кода ассоциированный с данным методом в момент вызова. На сегодняшнем занятии мы начнём знакомиться с некоторыми итераторами, доступными классу Array
. Рассмотреть все у нас не хватит с одной стороны времени, а с другой стороны некоторые нам сейчас просто не нужны.
Итератор #each
Данный итератор предназначен для обхода всех элементов массива. Он эффективно заменяет цикл for
, а также является основой для построения более сложных методов обработки наборов данных.
Код, приставленный ниже, выводит стандартный вывод строку a -- b -- c --
a = [ "a", "b", "c" ]
a.each {|x| print x, " -- " }
Итератор #each_with_index
Данный итератор позволяет получить доступ одновременно к значению элемена и его порядковому номеру. Плюс по сравнению с for
- никогда не выйдете за границы массива. Код ниже выводит строку 0: a -- 1: b -- 2: c --
a = [ "a", "b", "c" ]
a.each_with_index {|x, index| print "#{index}: #{x} -- " }
Итератор #reduce
или inject
Данный итератор предназначен для применения некоторой общей операции над всеми элементами массива. Большинство математических операторов, обладающих свойством коммуникативности, можно легко применить с помощью данного итератора.
Например, для сложения всех элементов массива достаточно вызвать итератор следующим образом:
[1, 2, 3, 4].reduce(0) { |sum, n| sum + n }
Блок, ассоциированный с методом будет получать 2 аргумента: промежуточный результат и очередное значение из массива. Блок должен использовать эти 2 аргумента и на их основании формировать новый промежуточный результат, который будет использован при следующей итерации.
В качестве аргумента данному методу можно передать начальное значение, которое необходимо использовать на первой итерации. В примере передаётся число 0, которое будет использовано на первой итерации. Если данное число не передавать, то первоначальным значнеием промежуточного результата станет первый элемент массива, а обход массива начнётся со 2 элемента.
Итератор #delete_if
Итератор позволяет удалить элемент из массива, если данный элемент удовлетворяет некоторому условию. Условие описывает программист в блоке, который ассоциируется с данным массивом. Если блок возвращает true
, то данный элемент будет удалён.
Метод вернёт массив, состоящий из удалённых элементов или nil
, если ничего не было удалено.
Альтернативное название для данного метода - reject!
.
scores = [ 97, 42, 75 ]
scores.delete_if {|score| score < 80 }
puts scores #=> [97]
Задачи
Задачи, предлагаемые на этом занятии, необходимо решить двумя способами. В первом способе вам предлагается использовать итераторы для решения поставленной задачи. Затем вы должны скопировать работающую программу и перейти на использование обычных циклов в ключевой логике по работе с массивами. Сравните решения между собой.
При разработке приложений придерживайтесь следующих правил:
- Разрабатывайте приложение как набор методов, вызывающих друг друга.
- Выделите методы, ответственные за ввод данных от пользователя, и методы, ответственные за обработку данных. Таким образом можно будет легко выполнить требование, указанное в начале раздела.
- Ввод данных должен осуществляться со стандартного потока ввода.
- Приложение должно печатать информационное сообщение о своей работе, а также приветствие при вводе данных.
- Если человек ошибся при вводе данных, то приложение должно ему сообщать об ошибке и просить повторить данную процедуру.
Решите следующие задачи с использованием итераторов:
- Задача №2 из пункта 1.3 задачника (стр. 15).
- Задача №3 из пункта 1.3 задачника (стр. 16).
- Задача №15 из пункта 1.3 задачника (стр. 16).
- Задача №4 из пункта 1.3 задачника (стр. 16).