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

мая 6, 2010  |  Published in Ruby, Основы  |  8 Comments

loopRuby очень богат на циклы, итераторы и другие синтаксические конструкции. Очень часто изучающие 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.

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

Данный пост - перевод статьи

Tags: ,

Responses

  1. MaxMaxter says:

    мая 20, 2010 at 22:58 (#)

    Если итераторы это такие же методы, то их можно и самому создавать? Вопрос: каким образом?

  2. admin says:

    мая 20, 2010 at 23:23 (#)

    MaxMaxter, методы-итераторы действительно можно определить самостоятельно, но практический смысл сего сомнителен,ведь Ruby предоставляет 99% всего, что вам может понадобиться. Итератор следует определять внутри модуля Enumerable, который включается во все класса исчисляемых(диапазоны, массивы, хэши). выглядит это таким образом:

    module Enumerable
    def our_iterator
    for element in self
    yield element
    end
    self
    end
    end

    Теперь метод our_iterator доступен для использования с массивами,хэшами и диапазонами. our_iterator имитирует поведение итератора each, выражение yield передает в блок кода текущий элемент element и выполняет блок кода для каждого элемента.

    В следующей я расскажу о замыканиях и там будут примеры использования их в итераторах и создание итераторов…

  3. says:

    июля 25, 2010 at 20:20 (#)

    Автор, у вас неправильный пример циклов while и until (запустите их), а так же совершенно неверное определение until

  4. admin says:

    июля 25, 2010 at 22:54 (#)

    Семён, я вам при много благодарен за то, что вы обратили мое внимание на ошибки в тексте, теперь они исправлены. Кстати, меня зовут Владимир ;-)

  5. says:

    августа 17, 2011 at 09:12 (#)

    Второй пример кода [language]loop do
    i+=1
    print «#{i}»
    break if i==10
    end[/language] НЕ напечатает 1 2 3 4 5 6 7 8 9 10, как написано в статье. Он напечатает 12345678910. Код криво скопипасчен из оригинала.

    Отступов нигде нету вообще — это печалит. В оригинале есть.

    Копировать Ваш код из текста статьи невозможно — строки кода перемежаются с номерами строк — приходится чистить.

    И сделайте нормальную авторизацию по твиттеру/жж/фацбуку/опенайди. 2011 год на дворе, а сайт просит мой емейл чтобы оставить одноразовый коммент. Стыдно, ей-богу. Хоть капчи нет — и на том спасибо.

  6. admin says:

    августа 17, 2011 at 10:29 (#)

    1. Да, там я немного затупил — сделал отступы между числами, чтобы было понетнее, что оно выводит.

    2. Отступы съедает вордпресс.

    3. Копировать возможно, для этого в подсветке кода есть специальная кнопка при клике на которую во всплывающем окне представляется код для копирования.

    4. К вордпрессу все плагины авторизации через соц. сети работают крайне коряво, да и мне не удалось найти такого, который бы объединял фейсбук, твиттер и вконтакт, а устанавливать все по отдельности — беда будет ибо они могут между собой конфликтовать, что-то мне подсказывает, что обязательно будет.

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

  7. says:

    декабря 3, 2011 at 09:31 (#)

    Немного непонятно, для изучающего недавно Ruby

  8. says:

    апреля 8, 2013 at 16:32 (#)

    .each_with_index, нет?

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.