Skip to content

Instantly share code, notes, and snippets.

@yesnik
Created December 23, 2014 05:03
Show Gist options
  • Save yesnik/60389cbc330352139b29 to your computer and use it in GitHub Desktop.
Save yesnik/60389cbc330352139b29 to your computer and use it in GitHub Desktop.
Задание по Ruby 1 (ответ)
require 'time'
module Converter
extend Enumerable
@@methods = {}
def new(*args, &block)
initialize(*args, &block)
end
def initialize(*args, &block)
@arr = args[0]
convert
end
def attrb(attribute, method=nil, &block)
method = block if block_given?
@@methods[attribute] = method
end
def convert
@arr.map do |hash|
convert_hash(hash)
end
@arr
end
def convert_hash(hash)
hash = hash.each do |k, v|
new_value = convert_item(k, v)
hash[k] = new_value if not new_value.nil?
end
end
def convert_item(k, v)
if @@methods[k.to_sym]
method = @@methods[k.to_sym]
if method.is_a? Proc
method.call(v)
else
v.send(method)
end
end
end
end
# Пример преобразователя:
class Transactions
extend Converter
#attrb :uid # значение будет передаваться как есть без преобразования
attrb :sum, :to_f # для преобразования значения будет вызван его(значения) метод to_f '50.25'.to_f
# для преобразования значения будет вызван блок,
# который принимает исходное значение и возвращает преобразованное
attrb (:timestamp) { |value| Time.parse(value) }
end
transactions = Transactions.new([
# входной массив хэшей где все значения - строки
{:uid => 'HT150', :sum => '50.25', :timestamp => '2014-04-04 05:50'},
{:uid => 'HT151', :sum => '119.63', :timestamp => '2014-04-04 06:18'}
])
# Значения преобразованы согласно правилам преобразованиия, описанным в Transactions
p transactions.to_a
# => [{:uid=>"HT150", :sum=>50.25, :timestamp=>2014-04-04 05:50:00 +0600},
# {:uid=>"HT151", :sum=>119.63, :timestamp=>2014-04-04 06:18:00 +0600}]
p transactions.first # => {:uid=>"HT150", :sum=>50.25, :timestamp=>2014-04-04 05:50:00 +0600}
p transactions.select { |tx| tx[:sum] > 100 }
# => [{:uid => 'HT151', :sum => 119.63, :timestamp => 2014-04-04 06:18:00 +0600}]
=begin
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Не могу понять, как нужно реализовать Converter,
чтобы можно было так вызвать метод sum
p transactions.sum # => 169.88
Такое возможно, если сделать include в класс Transactions
другого модуля, содержащего метод sum.
Однако в условии класс Transactions только
содержит extend модуля Converter.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=end
#Проверка
class People
extend Converter
attrb :name
attrb :height, :to_i
attrb :birthday do |value|
Date.parse(value)
end
end
# Он должен будет работать так:
ppl = People.new([{:name => 'Vlas', :height => '205', :birthday => '1990-08-08'}])
p ppl.first # => {:name => 'Vlas', :height => 205, :birthday => Wed, 08 Aug 1990}
@vkuznetsov
Copy link

В целом OK.

  • sum можно сделать в Transactions
  • Использование extend не принципиально, можно вместо этого делать include
  • не понял магию с new(_args, &block) -> initialize(_args, &block)
  • не понял зачем convert возращает старый массив
  • вместо if not следует писать unless
  • данным из внешних источников опасно делать to_sym, т.к. символы не вычищаются сборщиком мусора (будут вычищаться с версии 2.2)
  • пример, который не будет работать:
class ToFloat
  extend Converter
  attrb :value, :to_f
end

class ToArray
  extend Converter
  attrb(:value) { |v| [v] }
end

floats = ToFloat.new([{value: '10'}])
floats.first[:value] # =>  ["10"]   Ой-ой как же так?

class ToHash
  extend Converter
  attrb(:value) { |v| {v} }
end

floats = ToFloat.new([{value: '10'}])
floats.first[:value] # =>  {"10"=>"10"}  Да что ж это такое?

@yesnik
Copy link
Author

yesnik commented Dec 24, 2014

Спасибо большое за подробный комментарий! 😄 Выполнил работу над ошибками.

  • sum реализовал в Transactions
  • устранил магию с _new(_args, &block), initialize(args, &block), заменив на new(arr) -> initailize(arr)
  • convert возвращал массив, чтобы работали выражения, задействующие методы модуля Enumerable: transactions.first, transactions.select. Я отказался от включения Enumerable в модуль Converter, вместо этого реализовал методы, используемые в условии задачи: .to_a, .first, .select
  • вместо if not написал unless. Вопрос: имею ли я право писать "if !" или "if not"?
  • убрал преобразование ключей в символы. Теперь буду знать, что символы не вычищаются сборщиком мусора (до версии 2.2). Вопрос: получается, что в версии Ruby >= 2.2 в данном задании лучше преобразовать ключи в символы, чтобы улучшить скорость поиска значений по хэшу?
require 'time'

module Converter
  @@methods = {}

  def new(arr)
    initialize(arr)
    self
  end

  def initialize(arr)
    @arr = arr
    convert
  end

  def attrb(attribute, method=nil, &block)
    method = block if block_given?
    @@methods[attribute] = method
  end

  def convert
    @arr.map do |hash|
      convert_hash(hash)
    end
  end

  def convert_hash(hash)
    hash = hash.each do |k, v|
      new_value = convert_item(k, v)
      hash[k] = new_value unless new_value.nil?
    end
  end

  def to_a
    @arr
  end

  def first
    @arr[0]
  end

  def select(&block)
    @arr.select(&block)
  end

  def convert_item(k, v)
    if @@methods[k]
      method = @@methods[k]
      if method.is_a? Proc
        method.call(v)
      else
        v.send(method)
      end
    end
  end
end

# Пример преобразователя:
class Transactions
  extend Converter
  attrb :uid # значение будет передаваться как есть без преобразования
  attrb :sum, :to_f # для преобразования значения будет вызван его(значения) метод to_f '50.25'.to_f
  # для преобразования значения будет вызван блок, 
  # который принимает исходное значение и возвращает преобразованное
  attrb (:timestamp) { |value| Time.parse(value) }

  def self.sum
    @arr.inject(0) { |sum, hash| sum += hash[:sum] }
  end
end

transactions = Transactions.new([
  # входной массив хэшей где все значения - строки
  {:uid => 'HT150', :sum => '50.25', :timestamp => '2014-04-04 05:50'},
  {:uid => 'HT151', :sum => '119.63', :timestamp => '2014-04-04 06:18'}
])

# Значения преобразованы согласно правилам преобразованиия, описанным в Transactions
p transactions.to_a
# => [{:uid=>"HT150", :sum=>50.25, :timestamp=>2014-04-04 05:50:00 +0500}, 
#     {:uid=>"HT151", :sum=>119.63, :timestamp=>2014-04-04 06:18:00 +0500}]


p transactions.first 
# => {:uid=>"HT150", :sum=>50.25, :timestamp=>2014-04-04 05:50:00 +0500}

p transactions.select { |tx| tx[:sum] > 100 } 
# => [{:uid=>"HT151", :sum=>119.63, :timestamp=>2014-04-04 06:18:00 +0500}]

p transactions.sum
# => 169.88


#Тест 1
class People
  extend Converter
  attrb :name
  attrb :height, :to_i
  attrb :birthday do |value|
    Date.parse(value)
  end
end
# Он должен будет работать так:
ppl = People.new([{:name => 'Vlas', :height => '205', :birthday => '1990-08-08'}])
p ppl.first 
# => {:name=>"Vlas", :height=>205, :birthday=>#<Date: 1990-08-08 ((2448112j,0s,0n),+0s,2299161j)>}

# Тест 2
class ToFloat
  extend Converter
  attrb :value, :to_f
end
floats = ToFloat.new([{value: '10'}])
p floats.first[:value] # =>  10.0

# Тест 3
class ToArray
  extend Converter
  attrb(:value) { |v| [v] }
end
to_array = ToArray.new([{value: 1}])
p to_array.first[:value] # => [1]

# Тест 4
class ToHash
  extend Converter
  attrb(:value) { |v| {key: v} }
end
to_hash = ToHash.new([{value: 1}])
p to_hash.first[:value] # => {:key=>1}

@yesnik
Copy link
Author

yesnik commented Dec 24, 2014

В приведенном решении действительно лучше использовать модуль Enumerable. В этом случае для transactions можно будет использовать методы map, inject, all? и др. Для этого следует в модуль Converter включить класс Enumerable, а также обязательно в Converter реализовать метод each.

require 'time'

module Converter
  @@methods = {}
  include Enumerable

  def new(arr)
    initialize(arr)
  end

  def initialize(arr)
    @arr = arr
    convert
    self
  end

  def attrb(attribute, method=nil, &block)
    method = block if block_given?
    @@methods[attribute] = method
  end

  def convert
    @arr.map do |hash|
      convert_hash(hash)
    end
  end

  def convert_hash(hash)
    hash = hash.each do |k, v|
      new_value = convert_item(k, v)
      hash[k] = new_value unless new_value.nil?
    end
  end

  def to_a
    @arr
  end

  def each(&block)
    @arr.each(&block)
  end

  def convert_item(k, v)
    if @@methods[k]
      method = @@methods[k]
      if method.is_a? Proc
        method.call(v)
      else
        v.send(method)
      end
    end
  end
end

# Пример преобразователя:
class Transactions
  extend Converter
  attrb :uid # значение будет передаваться как есть без преобразования
  attrb :sum, :to_f # для преобразования значения будет вызван его(значения) метод to_f '50.25'.to_f
  # для преобразования значения будет вызван блок, 
  # который принимает исходное значение и возвращает преобразованное
  attrb (:timestamp) { |value| Time.parse(value) }

  def self.sum
    @arr.inject(0) { |sum, hash| sum += hash[:sum] }
  end
end

transactions = Transactions.new([
  # входной массив хэшей где все значения - строки
  {:uid => 'HT150', :sum => '50.25', :timestamp => '2014-04-04 05:50'},
  {:uid => 'HT151', :sum => '119.63', :timestamp => '2014-04-04 06:18'}
])

# Значения преобразованы согласно правилам преобразованиия, описанным в Transactions
p transactions.to_a
# => [{:uid=>"HT150", :sum=>50.25, :timestamp=>2014-04-04 05:50:00 +0500}, 
#     {:uid=>"HT151", :sum=>119.63, :timestamp=>2014-04-04 06:18:00 +0500}]


p transactions.first 
# => {:uid=>"HT150", :sum=>50.25, :timestamp=>2014-04-04 05:50:00 +0500}

p transactions.select { |tx| tx[:sum] > 100 } 
# => [{:uid=>"HT151", :sum=>119.63, :timestamp=>2014-04-04 06:18:00 +0500}]

p transactions.sum
# => 169.88


#Тест 1
class People
  extend Converter
  attrb :name
  attrb :height, :to_i
  attrb :birthday do |value|
    Date.parse(value)
  end
end
# Он должен будет работать так:
ppl = People.new([{:name => 'Vlas', :height => '205', :birthday => '1990-08-08'}])
p ppl.first 
# => {:name=>"Vlas", :height=>205, :birthday=>#<Date: 1990-08-08 ((2448112j,0s,0n),+0s,2299161j)>}

# Тест 2
class ToFloat
  extend Converter
  attrb :value, :to_f
end
floats = ToFloat.new([{value: '10'}])
p floats.first[:value] # =>  10.0

# Тест 3
class ToArray
  extend Converter
  attrb(:value) { |v| [v] }
end
to_array = ToArray.new([{value: 1}])
p to_array.first[:value] # => [1]

# Тест 4
class ToHash
  extend Converter
  attrb(:value) { |v| {key: v} }
end
to_hash = ToHash.new([{value: 1}])
p to_hash.first[:value] # => {:key=>1}

@vkuznetsov
Copy link

Никита, а ведь ты не решил проблему.

class ToFloat
  extend Converter
  attrb :value, :to_f
end

class ToArray
  extend Converter
  attrb(:value) { |v| [v] }
end

floats = ToFloat.new([{value: '10'}])
p floats.first[:value] # =>  ["10"]

определение класса ToArray изменяет поведение ToFloat

@yesnik
Copy link
Author

yesnik commented Dec 25, 2014

Владимир, спасибо большое за это замечание! Действительно, в модуле Converter используется переменная класса @@methods, которая была задумана для хранения методов, нужных для преобразования. Все бы ничего, однако данная переменная доступна для всех экземпляров классов, которые используют модуль Converter. Поэтому пришлось ее заменить на переменную экземпляра @methods, которая существует в рамках отдельно взятого экземпляра.
Исправленная версия модуля Converter:

require 'time'

module Converter

  include Enumerable

  def new(arr)
    initialize(arr)
  end

  def initialize(arr)
    @arr = arr
    convert
    self
  end

  def attrb(attribute, method=nil, &block)
    if @methods.nil?
      @methods = {}
    end
    method = block if block_given?
    @methods[attribute] = method unless method.nil?
  end

  def convert
    @arr.map do |hash|
      convert_hash(hash)
    end
  end

  def convert_hash(hash)
    hash = hash.each do |k, v|
      new_value = convert_item(k, v)
      hash[k] = new_value unless new_value.nil?
    end
  end

  def to_a
    @arr
  end

  def each(&block)
    @arr.each(&block)
  end

  def convert_item(k, v)
    if @methods[k]
      method = @methods[k]
      if method.is_a? Proc
        method.call(v)
      else
        v.send(method)
      end
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment