Богатство Ruby на циклы и итераторы
мая 6, 2010 | Published in Ruby, Основы | 8 Comments
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.
На этом все. Если я забыл что-то упомянуть – напишите это в комментариях к посту – я буду вам очень благодарен.
Данный пост - перевод статьи
мая 20, 2010 at 22:58 (#)
Если итераторы это такие же методы, то их можно и самому создавать? Вопрос: каким образом?
мая 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 и выполняет блок кода для каждого элемента.
В следующей я расскажу о замыканиях и там будут примеры использования их в итераторах и создание итераторов…
июля 25, 2010 at 20:20 (#)
Автор, у вас неправильный пример циклов while и until (запустите их), а так же совершенно неверное определение until
июля 25, 2010 at 22:54 (#)
Семён, я вам при много благодарен за то, что вы обратили мое внимание на ошибки в тексте, теперь они исправлены. Кстати, меня зовут Владимир ;-)
августа 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 год на дворе, а сайт просит мой емейл чтобы оставить одноразовый коммент. Стыдно, ей-богу. Хоть капчи нет — и на том спасибо.
августа 17, 2011 at 10:29 (#)
1. Да, там я немного затупил — сделал отступы между числами, чтобы было понетнее, что оно выводит.
2. Отступы съедает вордпресс.
3. Копировать возможно, для этого в подсветке кода есть специальная кнопка при клике на которую во всплывающем окне представляется код для копирования.
4. К вордпрессу все плагины авторизации через соц. сети работают крайне коряво, да и мне не удалось найти такого, который бы объединял фейсбук, твиттер и вконтакт, а устанавливать все по отдельности — беда будет ибо они могут между собой конфликтовать, что-то мне подсказывает, что обязательно будет.
5. Эмейл нужен для того, чтобы я мог вам отписаться на почту, вы вводите его один раз, дальше ваш браузер его запоминает и при следующем заходе ворма заполнится автоматически. Это не регистрация и не аутентификация, а просто указание реквизиитов автора комментария для обратной связи.
декабря 3, 2011 at 09:31 (#)
Немного непонятно, для изучающего недавно Ruby
апреля 8, 2013 at 16:32 (#)
.each_with_index, нет?