Богатство Ruby на циклы и итераторы

Ruby очень богат на циклы, итераторы и другие синтаксические конструкции...

Posted by Марк Мельник on May 6, 2010

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

В этой статье я познакомлю вас со всеми знакомыми мне циклами и некоторыми часто используемыми итераторами и приведу простые примеры их использования.

Цикл  Loop

Простейшая конструкция цикла в Ruby это метод loop. На самом деле это не конструкция цикла, а итератор, но из-за его простоты я напишу о нем в самом начале статьи.

 loop {puts "Hello Word!"}

То, что мы только что сделали – это бесконечный цикл. Пользы с такого цикла мало, но если вам вдруг понадобится создать бесконечный цикл вы уже будете знать самый простой способ его создать.

Ruby вместе с разнообразием циклов и прочих управляющих структур поставляет нам управляющие работой цикла ключевые слова, что позволяет нам создать цикл более полезный нежели тот, который я приводил выше. Эти слова – это: break, next, redo. Ключевое слово break позволяет нам выйти из цикла в любой момент.

loop do
i+=1
print "#{i}"
break if i==10
end

Данный код напечатает: 1 2 3 4 5 6 7 8 9 10

В данном примере цикл прекращает работу после того, как i станет равной 10.

Ключевое слово next используется для пропуска текущей итерации и перехода к следующей, например:

i=0
loop do
i+=1
next if i==5
print "#{i} "
break if i==10
end

Этот код напечатает числа от 1 до 10, но пропустит число 5: 1 2 3 4 6 7 8 9 10

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

i=0
puts(
loop do
i+=1
print "#{i} "
break 'Hello' if i==10
end)

Как не сложно догадаться, этот пример напечатает числа от 1 до 10, а затем напечатает «Hello».

Использование ключевого слова redo в циклах loop имеет мало смысла, поэтому я оставлю его на потом.

Цикл While

Цикл while — это цикл с предусловием, т.е. действие цикла продолжается до тех пор, пока условие верно. While абсолютно идентичен одноименным циклам в других языках программирования:

i=1
while i < 11
print "#{i} "
i+=1
end

Этот код напечатает числа от 1 до 10.

Цикл Until

Цикл until - очень похож на цикл while, с тем лишь отличием, что он выполняется до тех пор, пока условие НЕ верно.

i=1
until i > 10
print "#{i} "
i+=1
end

Как и ожидалось, цикл напечатает числа от 1 до 10.

Выше я упоминал о ключевом слове redo, но не привел пример его использования, сейчас отличный момент продемонстрировать его возможности. Ключевое слово redo позволяет перезапустить цикл с начала, при этом оценка условий не будет производиться.

i=1
until i > 10 print "#{i} "
i+=1
redo  if i > 10
end

Данный код позволяет нам создать бесконечный цикл, который даже будет инкриментировать значение i и будет продолжать вывод:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ...

Все это работает потому, что redo перезапускает цикл с метки, но не оценивает конечное условие. Поэтому, даже, невзирая на то, что i больше 10 цикл продолжает работать, переменная i продолжает увеличиваться на 1 при каждом проходе цикла.

Используем While и Until как модификаторы и симулируем Do...While

Ruby также позволяет вам использовать ключевые слова while и until как модификаторы, это означает, что мы можем использовать их постфиксную форму, т.е. ставить после выражения, так, как мы это делали в примерах выше с постфиксной формой if. Благодаря этому Ruby предоставляет нам возможность писать очень короткие циклы без потери читабельности кода, например:

i=0
print "#{i+=1} " while i < 10
#Вот еще пример:
i=0
print "#{i+=1} " until i == 10

Оба примера напечатают числа от 1 до 10 в одну строку.

Это свойство ключевых слов while и until также позволяет нам реализовать цикл do..while. В руби нет встроенной конструкции для цикла, тело которого выполнялось бы минимум один раз (как do...while в Си), но мы можем сделать вот что:

i=11
begin
print "#{i} "
i+=1
end while i < 10

Этот код выведет число 11 даже несмотря на то, что мы завершаем цикл пока верно условие, что i меньше 10. Цикл гарантированно выполняется 1 раз как и в случае с циклом until, где условие проверяется после выполнения каждого прохода, а не перед ними.

Мы можем реализовать цикл do..while с помощю until:

i=10
begin
print "#{i} "
i+=1
end until i == 11

Этот код напечатает 10 и затем цикл будет завершен.

Цикл For

Цикл For является циклической конструкцией, но действует подобно итераторам без фактического принятия блока. Цикл for можно увидеть при проходе по значениям диапазона:

for i in 1..10
print "#{i} "
end
#или по значениям в массиве:
for value in [1,2,3,4,5,6,7,8,9,10]
print "#{value} "
end

Оба примера выводят числа от 1 до 10.

Итератор Each

Сейчас мы подошли к итераторам. Итераторы - это методы, которые принимают блоки и выполняют код в блоках для элементов коллекций (массивов или хэшей). Простейший метод-итератор - это each. Все итерируемые объекты (массивы и хэши) в Ruby имеют метод each что позволяет нам пробегать по значениям в итерируемом массиве или хэше и делать что-то с каждым его элементом. Для примера, давайте пробежимся по массиву и каждый элемент будем передавать в блок, который будет их просто печатать на экран:

[1,2,3,4,5,6,7,8,9,10].each {|value| print "#{value} "}

Если вы желаете делать что-то более грандиозное, вы можете использовать do..end – синтаксическую форму блока и делать что угодно с каждым значением передаваемым в блок.
Итератор Times

Times – это метод класса Integer, т.е. целых чисел. Итератор times аналогичен циклу for в других языках и позволяет выполнять цикл и совершать определенные операции которые передаются ему в блоке N- число раз, например:

10.times {|i| print "#{i} "}

Это напечатает нам числа от 0 до 9.

Итераторы Upto и Step

Аналогичен циклу for и итератору times с тем лишь отличием от times, что есть возможность задавать стартовую позицию (в times старт начинается с ноля). Пример:

1.upto(10) {|i| print "#{i} "}

Этот напечатает то, что мы и ожидали:

1 2 3 4 5 6 7 8 9 10

Мы также можем задавать шаг для каждой итерации с помощью итератора step:

1.step(10, 2) { |i| print "#{i} "}

Итератор step эквивалентен циклу for с инкрементом равным 2, т.е. при каждой идерации i=+2 (увеличивается на 2). Результат выполнения итератора step:
1 3 5 7 9


Итератор Each_Index

Иногда мы имеем массив и нам необходимо итерировать не по значениям, а по индексам –для этого и предназначен итератор each_index:

array = [10,20,30,40,50,60,70,80,90,100]
array.each_index {|i| print "#{array[i]} "}

Блок принимает индексы элементов массива и мы используем его для печати значений элементов(бесполезный, но демонстрирующий суть пример). Результат выполнения кода:

10 20 30 40 50 60 70 80 90 100

Как ожидалось, вы вывели значения всех элементов массива.

Я забыл упомянуть о ключевом слове retry. Retry – это аналог redo c тем лишь отличием, что оно не может использоваться в циклах while и until (ключевое слово redo может использоваться также в итераторах и цикле for), кроме того, ключевое слово retry выполняет переоценку аргументов переданных итератору в отличие от redo.

На этом все. Если я забыл что-то упомянуть – напишите это в комментариях к посту – я буду вам очень благодарен.

Данный пост - перевод статьи A Wealth Of Ruby Loops And Iterators