04. Enumerable

04. Enumerable

04. Enumerable

22 октомври 2012

Тази седмица

Въпрос 1

Какво прави alias?

class Something
  def name() 'baba' end
  alias relative name
  def name() 'dyado' end
end

p Something.new.relative

Прави копие на метода

Въпрос 2

Каква е разликата между alias и alias_method?

alias_method е метод, докато alias е синтаксис

class Something
  1.upto(5).each do |index|
    alias_method "to_s_#{index}", :to_s
  end
end

Въпрос 3

Кой (и как) може да вика private методи?

class Something
  private

  def foo
  end
end
  • Могат да се викат от други методи на обекта
  • Могат да се видат от методи в наследници
  • Не могат да се викат с явен target (self.foo)

Въпрос 4

На какво могат да завършваш методите в Ruby?

  • На ? ако са предикати
  • На ! ако имат две версии
  • На = ако са setter
  • Технически погледнато, и на някои "оператори", напр. %

Въпрос 5

Какви са конвенциите за имена на методи, променливи, константи и имена на класове?

  • UpperCamelCase - константи (включва имена на класове)
  • normal_snake_case - променвили, методи
  • SCREAMING_SNAKE_CASE - константи, които не са имена на класове или модули

Коментари по първа задача

Object#tap

Object#tap извиква блока със себе си и връща обекта, на който е извикан.

array = [].tap do |items|
  items              # []
  items.equal? array # false

  items << 'foo'
  'other thing'
end

array                # ["foo"]

Object#tap

за debug-ване

Имате следния код

(1..10).select { |x| x.odd? }.map { |x| x ** 2 }

Искате да видите какво остава след select-а:

(1..10).select { |x| x.odd? }.tap { |x| p x }.map { |x| x ** 2 }

Object#tap

в Array#occurences_count от първа задача

class Array
  def occurences_count
    Hash.new(0).tap do |result|
      each { |item| result[item] += 1 }
    end
  end
end

Symbol#to_proc

Следните два реда са (почти) еквивалентни:

name = ->(object) { object.name }
name = :name.to_proc

Когато подавате блок на метод с &block, Ruby извиква #to_proc, ако block не е метод.

Съответно, следните два реда са еквивалентни

%w[foo plugh larodi].map { |s| s.length } # [3, 5, 6]
%w[foo plugh larodi].map(&:length)        # [3, 5, 6]

Symbol#to_proc

с повече от един аргумент

Всъщност, малко по сложно е:

block = ->(obj, *args) { obj.method_name *args }
block = :method_name.to_proc

Това значи, че може да направите така:

[{a: 1}, {b: 2}, {c: 3}].inject { |a, b| a.merge b } # {:a=>1, :b=>2, :c=>3}
[{a: 1}, {b: 2}, {c: 3}].inject(&:merge)             # {:a=>1, :b=>2, :c=>3}

Или дори:

[1, 2, 3, 4].inject { |a, b| a + b } # 10
[1, 2, 3, 4].inject(&:+)             # 10

Symbol#to_proc

Употреба

['Foo' :bar, 3].map(&:to_s).map(&:upcase)

Symbol#to_proc

Примерна имплементация

class Symbol
  def to_proc
    # ...?
  end
end

Object#send

3.send :+, 4 # 7

Symbol#to_proc

Примерна имплементация

class Symbol
  def to_proc
    ->(object, *args) { object.public_send self, *args }
  end
end

pry

$ gem install pry

Модули

Модулите в Ruby имат няколко предназначения:

Днес ще разгледаме последното.

Модули

като колекция от методи

Модулите в Ruby просто съдържат методи. Дефинират се подобно на класове:

module UselessStuff
  def almost_pi
    3.1415
  end

  def almost_e
    2.71
  end
end

Модули

миксиране

Модулите могат да се "миксират" с клас. Тогава той получава всички методи на модула като instance методи.

module UselessStuff
  def almost_pi
    3.1415
  end
end

class Something
  include UselessStuff
end

Something.new.almost_pi # 3.1415

Модули

self

В метод на модула, self е инстанцията, на която е извикан.

module Introducable
  def introduction
    "Hello, I am #{name}"
  end
end

class Person
  include Introducable
  def name() 'The Doctor' end
end

doctor = Person.new
doctor.introduction # "Hello, I am The Doctor"

Модули

приоритет на методите

Методите на класа имат приоритет пред методите на модула.

module Includeable
  def name() 'Module' end
end

class Something
  def name() 'Class' end
  include Includeable
end

Something.new.name # "Class"

Модули

приоритет на методите (2)

Ако два модула дефинират един и същи метод, ползва се последния:

module Chunky
  def name() 'chunky' end
end

module Bacon
  def name() 'bacon' end
end

class Something
  include Chunky
  include Bacon
end

Something.new.name # "bacon"

Модули

приоритет на методите (3)

Просто за информация: методите на mixin-ите имат приоритет пред тези на родителя.

Всичко това е свързано с нещо, наречено ancestor chain, за което ще си говорим следващия път.

Enumerable

#collect, #select и #inject

Помните ли тези методи?

[1, 2, 3, 4, 5].select(&:odd?)     # [1, 3, 5]
%w[foo plugh barney].map(&:length) # [3, 5, 6]
[1, 2, 3, 4, 5].inject(&:*)        # 120

Те са имплементирани в Enumerable, а не в Array.

Всяка колекция в Ruby ги има.

Други методи на Enumerable

all?          any?        chunk       collect          collect_concat
count         cycle       detect      drop             drop_while
each_cons     each_entry  each_slice  each_with_index  each_with_object
entries       find        find_all    find_index       first
flat_map      grep        group_by    include?         inject
map           max         max_by      member?          min
min_by        minmax      minmax_by   none?            one?
partition     reduce      reject      reverse_each     select
slice_before  sort        sort_by     take             take_while
to_a          zip 

После ще видите как генерирах тази таблица.

Hash < Enumerable

Хешовете също са Enumerable:

hash = {2 => 3, 4 => 5}

hash.to_a                                 # [[2, 3], [4, 5]]
hash.map { |p| p[0] + p[1] }              # [5, 9]
hash.map { |k, v| k + v }                 # [5, 9]
hash.inject(0) { |s, p| s + p[0] * p[1] } # 26

Hash < Enumerable

бележка под линия

Някои от Enumerable методите в Hash са предефинирани

hash = {2 => 3, 4 => 5, 6 => 7, 8 => 9}

hash.select { |k, v| v > 6 }      # {6=>7, 8=>9}
hash.to_a.select { |k, v| v > 6 } # [[6, 7], [8, 9]]

Enumerable#select връща списък, но Hash#select връща Hash.

Други неща за обхождане

#all? и #any?

#all?/#any? връщат истина ако всички/един елемент(и) от колекцията отговарят на някакво условие

[1, 2, 3, 4].all? { |x| x.even? } # false
[1, 2, 3, 4].any? { |x| x.even? } # true

[2, 4, 6, 8].all? { |x| x.even? } # true
[2, 4, 6, 8].any? { |x| x.odd? }  # false

# И разбира се:
[1, 2, 3, 4].any?(&:even?)        # true

#each_with_index

#each_with_index yield-ва всеки елемент с индекса му в масива

%w[foo bar baz].each_with_index do |word, index|
  puts "#{index}. #{word}"
end

Извежда:

0. foo
1. bar
2. baz 

#group_by

Името казва всичко, което ви е нужно да знаете

hash   = %w[foo bar plugh larodi]
groups = hash.group_by { |word| word.length }

groups # {3=>["foo", "bar"], 5=>["plugh"], 6=>["larodi"]}

#each_slice

#each_slice(n) yield-ва елементите на части по n:

%w[a b c d e f g h].each_slice(3) do |slice|
  p slice
end

Извежда

["a", "b", "c"]
["d", "e", "f"]
["g", "h"] 

#each_cons

#each_cons(n) yield "подмасиви" с n елемента

[1, 2, 3, 4, 5].each_cons(3) do |cons|
  p cons
end

Извежда

[1, 2, 3]
[2, 3, 4]
[3, 4, 5] 

#include? и #member?

Вече знаете какво прави

[1, 2, 3, 4].include? 3   # true
[1, 2, 3, 4].member? 5    # false

Двете са синоними

#zip

[1, 2, 3].zip([4, 5, 6])    # [[1, 4], [2, 5], [3, 6]]
[1, 2].zip([3, 4], [5, 6])  # [[1, 3, 5], [2, 4, 6]]

#one? и #none?

Като #all? и #any?, но в други случаи

%w[foo bar larodi].one? { |word| word.length == 6 }  # true
%w[foo bar larodi].one? { |word| word.length == 3 }  # false

[1, 5, 3].none? { |number| number.even? }   # true
[1, 2, 3].none? { |number| number.even? }   # false

#take, #drop, #take_while и #drop_while

[1, 2, 3, 4, 5].take(2)  # [1, 2]
[1, 2, 3, 4, 5].drop(2)  # [3, 4, 5]

[1, 3, 5, 6, 7, 9].take_while(&:odd?)  # [1, 3, 5]
[1, 3, 5, 6, 7, 9].drop_while(&:odd?) # [6, 7, 9]

Как генерирах таблицата с методите?

all?          any?        chunk       collect          collect_concat
count         cycle       detect      drop             drop_while
each_cons     each_entry  each_slice  each_with_index  each_with_object
entries       find        find_all    find_index       first
flat_map      grep        group_by    include?         inject
map           max         max_by      member?          min
min_by        minmax      minmax_by   none?            one?
partition     reduce      reject      reverse_each     select
slice_before  sort        sort_by     take             take_while
to_a          zip 

Как генерирах таблицата с методите?

кодът

Enumerable.instance_methods.
  sort.
  map { |name| name.to_s.rjust(16) }.
  each_slice(5) { |row| puts row.join '' }

Disclaimer: Леко редактирах whitespace-а за да се събере в слайд

include Enumerable

или как да го ползваме за наши класове

Трябва да дефинирате #each.

Той ви дава всичко останало.

include Enumerable

пример

class FibonacciNumbers
  include Enumerable

  def initialize(limit)
    @limit = limit
  end

  def each
    current, previous = 1, 0

    while current < @limit
      yield current
      current, previous = current + previous, current
    end
  end
end

FibonacciNumbers.new(100).to_a # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

include Enumerable

пример

class StepRange
  include Enumerable

  def initialize(first, last, step)
    @first, @last, @step = first, last, step
  end

  def each
    @first.step(@last, @step) { |n| yield n }
  end
end

StepRange.new(1, 10, 2).select { |n| n > 5 } # [7, 9]

Въпроси