├── .gitignore ├── Readme.md ├── collections ├── each.rb ├── map.rb ├── reduce.rb ├── reject.rb └── select.rb ├── cycles ├── for.rb ├── until.rb └── while.rb ├── iterators ├── downto.rb ├── each.rb ├── times.rb └── upto.rb ├── tree ├── tree.rb └── walk.rb └── yield ├── block_given.rb ├── coll.rb └── yield.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store* -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Итераторы в Ruby 2 | 3 | В Ruby практически не используются циклы, вся современная экосистема языка и приложения выстроены при помощи итераторов блоков. 4 | 5 | ## 1. Циклы Ruby 6 | 7 | В Ruby как и в любом языке программирования имеются операторы цикла, цель которых, выполнение повторяющихся участков кода. 8 | 9 | Например, для перебора элементов массива `[1, 2, 3, 4, 5]` можно воспользоваться циклом `for`: 10 | 11 | for x in [1, 2, 3, 4, 5] do 12 | puts x 13 | end 14 | 1 15 | 2 16 | 3 17 | 4 18 | 5 19 | 20 | Цикл пробегает значения от начала массива до последнего элемента, помещая на каждой итерации текущее значение из массива в переменную `x`. Когда значения в цикле заканчиваются, цикл завершает работу. 21 | 22 | Еще один оператор для выполнения циклов — это `while`. Например, следующий код выводит значения от 1 до 5: 23 | 24 | i = 1 25 | while i <= 5 do 26 | puts i 27 | i += 1 28 | end 29 | 1 30 | 2 31 | 3 32 | 4 33 | 5 34 | 35 | Цикл начинается с ключевого слова `while`, после которого размещено условие. Выражение с условием возвращает либо `true` (истина), либо `false` (ложь). Пока условие возвращает `true`, цикл выполняет выражения между ключевыми словами `do` и `end`. Как только условие возвращает `false`, а это происходит когда переменная i получает значение 6, он прекращает работу. 36 | 37 | Кроме оператора `while` существует оператор `until`, который противоположен `while`, так как выполняет блок до тех пор, пока условие ложно: 38 | 39 | i = 1 40 | until i > 5 do 41 | puts i 42 | i += 1 43 | end 44 | 1 45 | 2 46 | 3 47 | 4 48 | 5 49 | 50 | ## 2. Почему Ruby-исты не используют циклы? 51 | 52 | Перечисленные выше операторы трудно обнаружить в Ruby-коде, по крайней мере в коде конечных приложений. Вместо них, рубисты часто прибегают к итераторам, специальным методам, которые позволяют обходить коллекции. 53 | 54 | Рассмотрим типичный итератор `each`: 55 | 56 | [1, 2, 3, 4, 5].each do |i| 57 | puts i 58 | end 59 | 1 60 | 2 61 | 3 62 | 4 63 | 5 64 | 65 | Метод `each` является именно методом объекта `[1, 2, 3, 4, 5]`, а не специальной конструкцией языка, чуть позже мы попробуем писать свои собственные методы-итераторы. 66 | 67 | Конструкция между `do` и `end` называется блоком, и в отличие от одноименных конструкций в циклах имеет собственную область видимости, не может быть сокращена за счет удаление `do` (как в случае циклов), однако, может быть преобразована в краткую форму за счет использования фигурных скобок: 68 | 69 | [1, 2, 3, 4, 5].each { |i| puts i } 70 | 1 71 | 2 72 | 3 73 | 4 74 | 5 75 | 76 | К фигурным скобках обычно прибегают, когда блок состоит из одного выражения, в случае нескольких выражений используют полную форму блока, с использованием ключевых слов `do` и `end`. 77 | 78 | Итератор `each` применим, не только для массивов, но и для хэшей: 79 | 80 | {a: 'b', c: 'd'}.each { |key, value| puts "#{key}: #{value}" } 81 | a: b 82 | c: d 83 | 84 | Здесь блок принимает вместо одного, два параметра, `key` под ключ (:a, :c), `value` — под значения ('b', 'd'). 85 | 86 | Итераторы необязательно обслуживают коллекции, например, итератор `times` применяется к числам позволяет выполнить цикл указанное количество раз: 87 | 88 | 5.times { |i| puts i } 89 | 0 90 | 1 91 | 2 92 | 3 93 | 4 94 | 95 | Причем для вещественного числа метод `times` уже не сработает: 96 | 97 | (5.0).times { |i| puts i } 98 | NoMethodError: undefined method `times' for 5.0:Float 99 | 100 | Для итерирования от одного числа к другому можно воспользоваться методом `upto`: 101 | 102 | 5.upto(10) { |i| puts i } 103 | 5 104 | 6 105 | 7 106 | 8 107 | 9 108 | 10 109 | 110 | Метод `downto` позволяет наоборот пробегать числа с шагом минус один: 111 | 112 | 10.downto(5) { |i| puts i } 113 | 10 114 | 9 115 | 8 116 | 7 117 | 6 118 | 5 119 | 120 | Главное знать, какой итератор можно применять с текущим объектом. 121 | 122 | ## 3. Как определить какой из итераторов можно использовать? 123 | 124 | Как видно из предыдущего раздела итераторы можно применять не ко всем объектам. При поиске подходящего итератора в документации следует помнить, что Ruby является полностью объектно-ориентированным языком. Это упрощает работу с документацией. Так как любая практически любая конструкция языка, за исключением небольшого количества ключевых слов (тех операторов цикла `for`, `while`, `until`), является либо объектом, либо методом объекта. 125 | 126 | Например даже обычное сложение 127 | 128 | 5 + 2 129 | 7 130 | 131 | следует рассматривать как объектно-ориентированную операцию, заключающуюся в вызове метода с именем + в отношении объекта 5, с передачей методу аргумента 2 132 | 133 | 5.+(2) 134 | 7 135 | 136 | Разумеется на практике отдается предпочтение более привычной арифметической форме записи 5 + 2, хотя в случае Ruby вторая форма записи более каноническая и более точно отражает, что стоит за реальным вызовом. 137 | 138 | Почти каждый объект, с которым приходится иметь дело в Ruby, имеет метод `methods`, который возвращает список методов объекта. Например: 139 | 140 | 5.methods 141 | [:%, :&, :*, :+, :-, :/, :<, :>, :^, :|, :~, :-@, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :[], 142 | :inspect, :size, :succ, :to_s, :to_f, :div, :divmod, :fdiv, :modulo, :abs, :magnitude, :zero?, 143 | :odd?, :even?, :bit_length, :to_int, :to_i, :next, :upto, :chr, :ord, :integer?, :floor, :ceil, 144 | :round, :truncate, :downto, :times, :pred, :to_r, :numerator, :denominator, :rationalize, :gcd, 145 | :lcm, :gcdlcm, :+@, :eql?, :singleton_method_added, :coerce, :i, :remainder, :real?, :nonzero?, 146 | :step, :positive?, :negative?, :quo, :arg, :rectangular, :rect, :polar, :real, :imaginary, :imag, 147 | :abs2, :angle, :phase, :conjugate, :conj, :to_c, :between?, :iterator, :instance_of?, :public_send, 148 | :instance_variable_get, :instance_variable_set, :instance_variable_defined?, 149 | :remove_instance_variable, :private_methods, :kind_of?, :instance_variables, :tap, :is_a?, :extend, 150 | :define_singleton_method, :to_enum, :enum_for, :=~, :!~, :respond_to?, :freeze, :display, :send, 151 | :object_id, :method, :public_method, :singleton_method, :nil?, :hash, :class, :singleton_class, 152 | :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, 153 | :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :!=, :__send__, :equal?, 154 | :instance_eval, :instance_exec, :__id__] 155 | 156 | Полученный массив можно сортировать 157 | 158 | 5.methods.sort 159 | [:!, :!=, :!~, :%, :&, :*, :**, :+, :+@, :-, :-@, :/, :<, :<<, :<=, :<=>, :==, :===, :=~, :>, :>=, 160 | :>>, :[], :^, :__id__, :__send__, :abs, :abs2, :angle, :arg, :between?, :bit_length, :ceil, :chr, 161 | :class, :clone, :coerce, :conj, :conjugate, :define_singleton_method, :denominator, :display, :div, 162 | :divmod, :downto, :dup, :enum_for, :eql?, :equal?, :even?, :extend, :fdiv, :floor, :freeze, :frozen?, 163 | :gcd, :gcdlcm, :hash, :i, :imag, :imaginary, :inspect, :instance_eval, :instance_exec, :instance_of?, 164 | :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, 165 | :integer?, :is_a?, :iterator, :itself, :kind_of?, :lcm, :magnitude, :method, :methods, :modulo, 166 | :negative?, :next, :nil?, :nonzero?, :numerator, :object_id, :odd?, :ord, :phase, :polar, :positive?, 167 | :pred, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :quo, 168 | :rationalize, :real, :real?, :rect, :rectangular, :remainder, :remove_instance_variable, :respond_to?, 169 | :round, :send, :singleton_class, :singleton_method, :singleton_method_added, :singleton_methods, 170 | :size, :step, :succ, :taint, :tainted?, :tap, :times, :to_c, :to_enum, :to_f, :to_i, :to_int, :to_r, 171 | :to_s, :truncate, :trust, :untaint, :untrust, :untrusted?, :upto, :zero?, :|, :~] 172 | 173 | Или отфильтровать при помощи метода `grep` 174 | 175 | 5.methods.grep :downto 176 | [:downto] 177 | 178 | Метод `grep` допускает использование регулярных выражений, например, следующий вызов вернет список методов, начинающихся с символа d 179 | 180 | 5.methods.grep /^d.*/ 181 | [:div, :divmod, :downto, :denominator, :define_singleton_method, :display, :dup] 182 | 183 | Кроме того, всегда можно запросить у текущего объекта его класс при помощи одноименного метода `class`: 184 | 185 | 5.class 186 | Fixnum 187 | 188 | Получив имя класса, в [документации](https://ruby-doc.org/core-2.3.0/) всегда можно уточнить список его методов. 189 | 190 | ## 4. Итераторы коллекций 191 | 192 | Так как в Ruby практически все является объектом, сам язык и его библиотеки предоставляют большое количество предопределенных объектов. Многие из них содержат итераторы. Особенно много итераторов содержат объекты коллекций, так как это основной и предпочтительный способ для манипулированием содержимым коллекции. 193 | 194 | Среди итераторов выделяют несколько основополагающих, знать которые должен любой Ruby-разработчик: 195 | 196 | * [each](https://ruby-doc.org/core-2.3.0/Array.html#method-i-each) 197 | * [map](https://ruby-doc.org/core-2.3.0/Enumerable.html#method-i-map) (синоним collect) 198 | * [select](https://ruby-doc.org/core-2.3.0/Enumerable.html#method-i-select) (синоним find_all) 199 | * [reject](https://ruby-doc.org/core-2.3.0/Enumerable.html#method-i-reject) (синоним delete_if) 200 | * [reduce](https://ruby-doc.org/core-2.3.0/Enumerable.html#method-i-reduce) (синоним inject) 201 | * [tap](https://ruby-doc.org/core-2.3.0/Object.html#method-i-tap) 202 | 203 | Давайте посмотрим, как они работают. Итератор `map` очень похож на `each`, однако, если `each` всегда возвращает исходный массив, то `map` возвращает массив, состоящий из такого же количества элементов, что и исходный, однако, в качестве элементов используется содержимое, вычисленное в блоке, например: 204 | 205 | [1, 2, 3, 4, 5].each { |x| x + 1 } 206 | [1, 2, 3, 4, 5] 207 | 208 | [1, 2, 3, 4, 5].map { |x| x + 1 } 209 | [2, 3, 4, 5, 6] 210 | 211 | или вообще так 212 | 213 | [1, 2, 3, 4, 5].map { |x| 1 } 214 | [1, 1, 1, 1, 1] 215 | 216 | У `map` есть синоним `collect`, однако рубисты в массе своей предпочитают более короткий в написании `map`. Это вообще общее правило, чем короче конструкция в написании тем она более популярна. 217 | 218 | Таким образом map возвращает массив с тем же количеством элементов, что и исходный, но в качестве элементов которого выступает содержимое блоков. 219 | 220 | Для того, чтобы отфильтровать содержимое массива используется пара итераторов `select` и `reject`. Блоки в этих итераторах должны возвращать `true` или `false`, `select` — возвращает те элементы коллекции, для которых блок возвращает `true`, а `reject` — `false`. Давай посмотрим это на примере уже рассмотренного выше примера, отбирая четные номера. 221 | 222 | [1, 2, 3, 4, 5].select { |x| x.even? } 223 | [2, 4] 224 | 225 | Чтобы было понятнее, давайте, заменим `select` на `map`: 226 | 227 | [1, 2, 3, 4, 5].map { |x| x.even? } 228 | [false, true, false, true, false] 229 | 230 | Т.е. `select` отбирает второй и четвертый элемент коллекции. Итератор `reject`, наоборот, выбирает `false`: 231 | 232 | [1, 2, 3, 4, 5].reject { |x| x.even? } 233 | [1, 3, 5] 234 | 235 | Когда в блоке вызывается единственный метод, как в примере выше, можно воспользоваться сокращенной формой: 236 | 237 | [1, 2, 3, 4, 5].reject(&:even?) 238 | [1, 3, 5] 239 | 240 | Пока будем избегать такой записи, однако, в реальных приложениях она часто встречается, в силу того, что короче полного варианта. 241 | 242 | Итераторы можно комбинировать друг с другом, например, если требуется извлечь квадраты нечетных чисел, можно воспользоваться следующей комбинацией методов `map` и `select`: 243 | 244 | [1, 2, 3, 4, 5].reject { |x| x.even? }.map { |x| x * x} 245 | [1, 9, 25] 246 | 247 | Еще один итератор, который обязательно нужно рассмотреть — это `reduce`, суть его это накопление результатов, по мере обхода коллекции. Например, перемножив элементы коллекции друг на друга можно получить факториал: 248 | 249 | [1, 2, 3, 4, 5].reduce { |fact, x| fact * x } 250 | 120 251 | 252 | Можем перепроверить, перемножив элементы коллекции без итератора 253 | 254 | 1 * 2 * 3 * 4 * 5 255 | 120 256 | 257 | Итератор `reduce`, на первый взгляд может показаться магическим: непонятно как инициализируется переменная `fact` и как вообще она ведет себя в итераторе. Дело в том, что в такой форме переменная-аккумулятор `fact` инициализируется первым элементом коллекции. На каждой новой итерации, переменной присваивается результат вычисления блока. В качестве результата итератор возвращает значение переменной `fact`. Можно и явно инициировать переменную-аккумулятор, если передать итератору `reduce` аргумент: 258 | 259 | [1, 2, 3, 4, 5].reduce(2) { |fact, x| fact * x } 260 | 240 261 | 262 | В случае `inject` тоже есть сокращенные варианты и их тоже стоит иметь в виду, когда вы читаете код Ruby-проектов: 263 | 264 | [1, 2, 3, 4, 5].reduce(:*) 265 | 266 | Итератор `tap` предназначен для выполнения побочного действия в цепочке других итераторов. Для того чтобы узнать промежуточное значение, которое возвращает итератор `reject` в следующем примере, можно воспользоваться итератором `tap`: 267 | 268 | [1, 2, 3, 4, 5].reject { |x| x.even? }.tap{ |x| p x }.map { |x| x * x} 269 | [1, 3, 5] 270 | => [1, 9, 25] 271 | 272 | ## 5. Антипатерны 273 | 274 | Очень часто, особенно при первом знакомстве с итератором начинающие разработчики используют один итератор. Чаще всего итератор `each`, в результате получается код, который можно сделать короче 275 | 276 | arr = [] 277 | [1, 2, 3, 4, 5].each do |x| 278 | arr << x * x 279 | end 280 | p arr 281 | [1, 4, 9, 16, 25] 282 | 283 | В случае когда пустой массив, заполняется внутри какого либо итератора, как правило, есть более подходящий итератор или комбинация итераторов, позволяющих сократить код, при помощи итератора `map`, `select` или `reject`: 284 | 285 | arr = [1, 2, 3, 4, 5].map { |x| x * x } 286 | p arr 287 | [1, 4, 9, 16, 25] 288 | 289 | Если внутри метода изменению подвергается хэш и в конце метода возникает явно вернуть переменную хэша 290 | 291 | def change_hast(params) 292 | params[:page] = 1 293 | params 294 | end 295 | 296 | Как правило можно сократить код, воспользовавшись итератором tap, который всегда возвращает значение своего объекта 297 | 298 | def change_hast(params) 299 | params.tap{ |p| p[:page] = 1 } 300 | end 301 | 302 | ## 6. Итераторы изнутри: оператор yield и модуль Enumerable 303 | 304 | Для того, чтобы получить возможность создавать свои собственные итераторы, потребуется задействовать оператора `yield`, который позволяет передать управление из текущего метода во внешний код: 305 | 306 | def iterator 307 | yield 'hello' 308 | yield 'world' 309 | end 310 | 311 | В методе `iterator` оператор `yield` вызывается два раза. Это приводит к тому, что во-первых метод не работает без блока: 312 | 313 | iterator 314 | LocalJumpError: no block given (yield) 315 | 316 | Во-вторых, если блок методу передается, он выполняется ровно два раза, по количеству вызовов оператора `yield`: 317 | 318 | iterator { |word| puts word } 319 | hello 320 | world 321 | 322 | Оператор `yield` может принимать переменное количество аргументов. 323 | 324 | def iterator2 325 | yield 'hello', 'world' 326 | end 327 | 328 | Сколько аргументов передано `yield`, такое количество параметров должно быть в вызове блока: 329 | 330 | iterator2 { |hello, word| puts “#{hello} #{word}” } 331 | hello world 332 | 333 | Таким образом, для создания собственного итератора, например, того же `each` нужно просто вызвать оператор `yield` для каждого из элементов коллекции. Создадим для этого специальный класс `Coll`. 334 | 335 | class Coll 336 | def initialize(coll = []) 337 | @arr = coll 338 | end 339 | 340 | def each 341 | for x in @arr do 342 | yield x 343 | end 344 | end 345 | end 346 | 347 | Обычно в языках программирования для описания свойств и поведения переменных используется тип. Класс выполняет схожую функцию, предоставляя код описания объектов. Метод `initialize` является конструктором и вызывается до всех других методов объекта, во время создания объекта методом `new`. 348 | 349 | Единственная задача конструктора из примера, инициировать переменную объекта или как говорят инстанс-переменную `@arr`. Такие переменные — это переменные на уровне объекта, фактически это более короткий вариант `self.arr`. Сейчас не будем подробно останавливаться на объектно-ориентированной модели Ruby, так как язык полностью объектно-ориентированный и его объектно-ориентированные возможности мягко говоря богаты. 350 | 351 | Помимо конструктора в классе определен метод `each`, который ведет себя как итератор, т.е. последовательно пробегает от первого элемента массива до последнего, передавая управление во внешний блок при помощи оператора `yield`. Рассмотрим как работает класс `Coll`, для этого создадим его объект. 352 | 353 | obj = Coll.new([1, 2, 3, 4, 5]) 354 | 355 | Теперь можно воспользоваться итератором `each`: 356 | 357 | obj.each { |x| puts x + 1 } 358 | 2 359 | 3 360 | 4 361 | 5 362 | 6 363 | 364 | Воспользоваться итератором map уже не получится, так как он просто не объявлен в классе `Coll`. 365 | 366 | obj.map { |x| x + 1 } 367 | NoMethodError: undefined method `map' for # 368 | 369 | По идее нам нужно объявлять каждый итератор, который может потребоваться в дальнейшей работе. Если мы сейчас попробуем обратиться к `map`, то не сможем обнаружить его. К счастью, стандартная библиотека Ruby облегчает нам работу, достаточно реализовать один единственный метод each и подключить к классу модуль `Enumerable` и все базовые итераторы, `map`, `select`, `reject` и т.д. будут построены из метода `each` автоматически. 370 | 371 | class Coll 372 | include Enumerable 373 | 374 | def initialize(coll = []) 375 | @arr = coll 376 | end 377 | 378 | def each 379 | for x in @arr do 380 | yield x 381 | end 382 | end 383 | end 384 | obj = Coll.new([1, 2, 3, 4, 5]) 385 | obj.map { |x| x + 1 } 386 | [2, 3, 4, 5, 6] 387 | 388 | Когда мы говорим о создании собственных итераторов, следует упомянуть метод `block_given?`, который проверяет передан ли текущему методу блок или нет. Это позволяет вместо завершении метода выбросом исключения совершить какое-то разумное действие. Например, вместо передачи управления в блок, просто вернуть коллекцию в виде массива. Вот здесь представлен метод `block`, который при помощи метода `block_given?` и оператора `if` позволяет обработать ситуацию передачи блока. 389 | 390 | def block(arr = []) 391 | if block_given? 392 | arr.each { |x| yield x } 393 | else 394 | arr 395 | end 396 | end 397 | 398 | ## 7. Реальный пример обход дерева каталога 399 | 400 | Рассмотрим более сложный пример, пусть у нас имеется дерево с директориями и названиями файлов. Нам требуется рекурсивно обойти дерево, отобрав только файлы. Чтобы было интереснее пусть у нас сложилась ситуация, когда мы не можем воспользоваться стандартными средствами, классами `Dir` или `Find` и вообще дерево сформировано в виде массива, в котором элементы могут быть либо строками `String`, либо хэшами в которых ключи названия папок, а массивы — список файлов в папке или опять же подкаталоги. 401 | 402 | tree = [ 403 | 'index.rb', 404 | { 405 | 'src' => [ 406 | 'file01.rb', 407 | 'file02.rb', 408 | 'file03.rb' 409 | ] 410 | }, 411 | { 412 | 'doc' => [ 413 | 'file01.md', 414 | 'file02.md', 415 | 'file03.md', 416 | { 417 | 'details' => [ 418 | 'index.md', 419 | 'arch.md' 420 | ] 421 | } 422 | ] 423 | } 424 | ] 425 | 426 | Обход дерева при помощи итератора each не очень интересен, так как он затрагивает только самый верхний уровень массива. 427 | 428 | tree.each { |x| puts x } 429 | index.rb 430 | {"src"=>["file01.rb", "file02.rb", "file03.rb"]} 431 | {"doc"=>["file01.md", "file02.md", "file03.md", {"details"=>["index.md", "arch.md"]}]} 432 | 433 | Однако, при помощи метода `is_a?` мы можем определить тип объекта, например, следующий код позволяет определить, что перед нами "файл" или "папка". 434 | 435 | tree.each do |x| 436 | puts 'Dir' if x.is_a? Hash 437 | puts 'File' if x.is_a? String 438 | end 439 | 440 | Таким образом на верхнем уровне, можно различать файлы и папки. Если перед нами файл, можно передать его во внешний блок при помощи `yield`, если перед нами папка — можно повторить операцию сканирования. Таким образом, мы приходим к тому, что итератор должен быть рекурсивным, т.е. вызывать самого себя. Пробежались по элементам коллекции: для строк вернули блок, для массивов опять вызывали это же самый метод и так, до тех пор, пока не доберемся до самых дальних уголков дерева. 441 | 442 | Сложность заключается в том, что при вызове оператора `yield` управление передается лишь непосредственно в точку вызова. 443 | 444 | def walk(arr = [], &proc) 445 | arr.each do |el| 446 | proc.call(el) if el.is_a? String 447 | el.each { |_dir, files| walk(files, &proc) } if el.is_a? Hash 448 | end 449 | end 450 | 451 | Т.е. во второй строке each-блока, там где итератор `walk` вызывается для очередного списка файлов `files` необходимо вызывать блок. Для того, чтобы этого не делать, а передать блок наружу, пригодятся альтернативный синтаксис передачи блоков. Для этого последний параметр метода `walk` предваряется амперсандом `&proc`, в результате внутри функции блок становится именованным Proc-объект. 452 | 453 | Во-первых его можно передать в другие методы или вглубь рекурсивного вызова, как в случае walk-итератора. 454 | 455 | Во-вторых вместо оператора `yield` можно использовать proc-объект, вызвав у него метод `call`, т.е. 456 | 457 | def walk(arr = [], &proc) 458 | arr.each do |el| 459 | proc.call(el) if el.is_a? String 460 | el.each { |_dir, files| walk(files, &proc) } if el.is_a? Hash 461 | end 462 | end 463 | 464 | Теперь можно разобрать дерево 465 | 466 | walk(tree) { |file| puts file } 467 | index.rb 468 | file01.rb 469 | file02.rb 470 | file03.rb 471 | file01.md 472 | file02.md 473 | file03.md 474 | index.md 475 | arch.md -------------------------------------------------------------------------------- /collections/each.rb: -------------------------------------------------------------------------------- 1 | [1, 2, 3, 4, 5].each { |x| puts x + 1 } 2 | -------------------------------------------------------------------------------- /collections/map.rb: -------------------------------------------------------------------------------- 1 | [1, 2, 3, 4, 5].map { |x| x + 1 } 2 | [1, 2, 3, 4, 5].collect { |x| x + 1 } 3 | -------------------------------------------------------------------------------- /collections/reduce.rb: -------------------------------------------------------------------------------- 1 | [1, 2, 3, 4, 5].reduce { |fact, x| fact * x } 2 | [1, 2, 3, 4, 5].inject { |fact, x| fact * x } 3 | -------------------------------------------------------------------------------- /collections/reject.rb: -------------------------------------------------------------------------------- 1 | [1, 2, 3, 4, 5].reject { |x| x.even? } 2 | [1, 2, 3, 4, 5].delete_if { |x| x.even? } -------------------------------------------------------------------------------- /collections/select.rb: -------------------------------------------------------------------------------- 1 | [1, 2, 3, 4, 5].select { |x| x.even? } 2 | [1, 2, 3, 4, 5].find_all { |x| x.even? } 3 | -------------------------------------------------------------------------------- /cycles/for.rb: -------------------------------------------------------------------------------- 1 | for el in [1, 2, 3, 4, 5] do 2 | puts el 3 | end 4 | -------------------------------------------------------------------------------- /cycles/until.rb: -------------------------------------------------------------------------------- 1 | i = 1 2 | until i > 5 do 3 | puts i 4 | i += 1 5 | end 6 | -------------------------------------------------------------------------------- /cycles/while.rb: -------------------------------------------------------------------------------- 1 | i = 1 2 | while i <= 5 do 3 | puts i 4 | i += 1 5 | end 6 | -------------------------------------------------------------------------------- /iterators/downto.rb: -------------------------------------------------------------------------------- 1 | 10.downto(5) { |i| puts i } -------------------------------------------------------------------------------- /iterators/each.rb: -------------------------------------------------------------------------------- 1 | [1, 2, 3, 4, 5].each do |i| 2 | puts i 3 | end 4 | -------------------------------------------------------------------------------- /iterators/times.rb: -------------------------------------------------------------------------------- 1 | 5.times { |i| puts i } -------------------------------------------------------------------------------- /iterators/upto.rb: -------------------------------------------------------------------------------- 1 | 5.upto(10) { |i| puts i } -------------------------------------------------------------------------------- /tree/tree.rb: -------------------------------------------------------------------------------- 1 | tree = [ 2 | 'index.rb', 3 | { 4 | 'src' => [ 5 | 'file01.rb', 6 | 'file02.rb', 7 | 'file03.rb' 8 | ] 9 | }, 10 | { 11 | 'doc' => [ 12 | 'file01.md', 13 | 'file02.md', 14 | 'file03.md', 15 | { 16 | 'details' => [ 17 | 'index.md', 18 | 'arch.md' 19 | ] 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /tree/walk.rb: -------------------------------------------------------------------------------- 1 | def walk(arr = [], &proc) 2 | arr.each do |el| 3 | yield el if el.is_a? String 4 | el.each { |_dir, files| walk(files, &proc) } if el.is_a? Hash 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /yield/block_given.rb: -------------------------------------------------------------------------------- 1 | def block(arr = []) 2 | if block_given? 3 | arr.each { |x| yield x } 4 | else 5 | arr 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /yield/coll.rb: -------------------------------------------------------------------------------- 1 | class Coll 2 | def initialize(coll = []) 3 | @arr = coll 4 | end 5 | 6 | def each 7 | for x in @arr do 8 | yield x 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /yield/yield.rb: -------------------------------------------------------------------------------- 1 | def iterator 2 | yield 'hello' 3 | yield 'world' 4 | end 5 | --------------------------------------------------------------------------------