Evgenii Legotckoi
25 июня 2016 г. 12:22

User Guide #09 - Ruby - итераторы

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

Глагол iterate означает делать какую-либо вещь множество раз, и вы знаете, что итератор является чем-то, что выполняет некоторую вещь много раз.

Когда мы пишем код, мы должны работать с циклами во многих ситуациях. В C мы будем использовать for или while. Например,

  1. char *str;
  2. for (str = "abcdefg"; *str != '\0'; str++) {
  3. /* здесь идёт обработка символа */
  4. }

В C синтаксис for(...) предоставляет абстракцию для помощью с созданием цикла, но проверка str на null символ требует от программиста знать детали об внутренней структуре строки. Это создаёт ощущение того, что C низкоуровневый язык. Языки высокого уровня отмечены своей более гибкой поддержкой строк. Рассмотрим следующий shell скрипт sh* :

  1. #!/bin/sh
  2.  
  3. for i in *.[ch]; do
  4. # ... here would be something to do for each file
  5. done

Все исходные и заголовочные файлы C обрабатываются в текущей директории, и команда shell обрабатывает детали файлов подбирая и изменяя имена файлов один за другим. Этот уровень является более высоким, чем в C, не так ли?

Но имеется кое-что большее для рассмотрения: в то время как  это отлично подходит для языка, который обеспечивает итераторы для встроенных типов данных, мы имеем разочарование в том случае, если мы должны вернуться к написанию низкоуровневых циклов для наших собственных типов данных. В ООП пользователи часто определяют другие типы данных, с чем могут быть связаны серьёзные проблемы.

Итак, объектно-ориентированный язык включается в себя некоторые возможности для итерации. Некоторые языки предоставляют специальные классы для этого. А Ruby позволяет напрямую определять итераторы.

Строка Ruby имеет некоторые полезные итераторы:

  1. ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n"
  2. <a><b><c>
  3. nil

each_byte является итератором для каждого символа в строке. Каждый символ помещается в локальную переменную c . Это может быть транслировано в нечто похожее на C код ...

  1. ruby> s="abc";i=0
  2. 0
  3. ruby> while i<s.length
  4. | printf "<%c>", s[i]; i+=1
  5. | end; print "\n"
  6. <a><b><c>
  7. nil

... однако, итератор each_byte является как концептуально проще, так и более предпочтительным для продолжения работы, даже если класс String будет радикально модифицирован в будущем Одним из преимуществ итераторов является то, что они более надёжны в условиях таких изменений. Что в действительности является хорошей характеристикой кода. (Да, наберитесь терпения, мы поговорим ещё о том, что такое классы)

Другим итератором String является each_line.

  1. ruby> "a\nb\nc\n".each_line{|l| print l}
  2. a
  3. b
  4. c
  5. nil

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

Оператор for, появившийся в предыдущей главе выполняет итерацию подобно итератору each . Строковый each работает подобно each_line , давайте перепишем ниже пример с применением for :

  1. ruby> for l in "a\nb\nc\n"
  2. | print l
  3. | end
  4. a
  5. b
  6. c
  7. nil

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

  1. ruby> c=0
  2. 0
  3. ruby> for i in 0..4
  4. | print i
  5. | if i == 2 and c == 0
  6. | c = 1
  7. | print "\n"
  8. | retry
  9. | end
  10. | end; print "\n"
  11. 012
  12. 01234
  13. nil

Иногда встречается оператор yield в определении итератора. yield перемещает управление в блок кода, который был передан итератору ( это будет подробно рассмотрено в главе об обработке объектов). Следующий пример показывает итератор repeat , который повторяет блок кода столько раз, сколько было указано в аргументе.

  1. ruby> def repeat(num)
  2. | while num > 0
  3. | yield
  4. | num -= 1
  5. | end
  6. | end
  7. nil
  8. ruby> repeat(3) { print "foo\n" }
  9. foo
  10. foo
  11. foo
  12. nil

С retry , мы можем определить итератор, которые работает как цикл while , хотя это и слишком медленно, чтобы быть практичным.

  1. ruby> def WHILE(cond)
  2. | return if not cond
  3. | yield
  4. | retry
  5. | end
  6. nil
  7. ruby> i=0; WHILE(i<3) { print i; i+=1 }
  8. 012 nil

Вы поняли, что является итератором? Имеется несколько ограничений в оригинальных итераторах, но фактически, всякий раз, когда вы определяете новый тип данных, часто бывает удобно определить подходящие итераторы для этого. В этом случае, приведённые выше примеры не очень полезны. Мы можем поговорить об этом после того, как лучше поймем, что такое классы.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

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