04. Enumerable

04. Enumerable

04. Enumerable

24 октомври 2011

Тази седмица

Въпрос 1

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

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

p Something.new.relative

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

Въпрос 2

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

class Something
  private

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

Въпрос 3

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

  • На ? ако са предикати
  • На ! ако имат две версии
  • На = ако са setter

Въпрос 4

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

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

Втора задача

GitHub repo-та

свързани с курса

Следните хранилища може да са ви интересни

Какво да правим с GitHub хранилищата?

Някои от възможните интеракции:

Решения на първа задача

Ще разгледаме:

Грешки

Array#flatten

class Array
  def to_hash
    Hash[*self.flatten]
  end
end

[[1, [2, 3]], [4, [5, 6]]].to_hash # {1=>2, 3=>4, 5=>6}

Ако някой от елементите е масив, номера няма да мине

Грешки

for

def to_hash
  result = Hash.new
  for i in 0...self.size
    result[self[i][0]] = self[i][1]
  end
  result
end

Не ползвайте for - остаряла форма е и вътрешно вика #each.

Hash.new е глупав начин да напишеше {}

self.each do |key, value| щеше да е напълно достатъчен

Грешки

идентация

class Array
    def occurences_count
        res = Hash.new(0)
        self.map { |elem| res[elem] += 1 }
        res
    end
end

Грешки

идентация пак

Някои от вас смесват табулации и интервали:

Накратко, научете се как да ползвате редакторите си.

Грешки

цикли

class Array
  def index_by
    h=Hash.new
    while not(self.empty?)
      x=self.first
      h[yield x]=x
      self.shift
    end
    puts h
  end
end

Грешки

кратки имена

def to_hash
  res = {}
  self.each do |v|
    res[v[0]] = v[1]
  end
  res
end

Грешки

криптични имена

def subarray_count(subarray)
  raise ArgumentException if subarray.empty?
  to_return = 0
  victim = self.dup
  while victim.length >= subarray.length do
    to_return += 1  if victim.first(subarray.length) == subarray
    victim.shift
  end
  to_return
end

victim? Наистина?

Грешки

много код

def subarray_count(subarray)
  result = 0
  for i in 0...size
    fail = 0
    for j in 0...subarray.size
      unless self[i+j] == subarray[j]
        fail = 1
      end
    end
    unless fail == 1
      result += 1 
    end
  end
  result
end

Това не е Ruby програма. Това е C програма. При това - крива.

Грешки

#inject за хешове

class Array
  def to_hash
    self.inject({}) { |k, v| k[v[0]] = v[1]; k }
  end

  def index_by
    self.inject({}) { |k, v| k.merge!(yield(v) => v) }
  end
end

Избягвайте да мутира аргумента на #inject

Тук #merge! няма работа

Грешки

странни скоби

class Array
  def index_by(&block)
    ((map &block).zip self).to_hash
  end
end

Имаш предвид map(&block).zip(self).to_hash ?

Впрочем, хитро решение

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

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 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]

Въпроси