Решение на Втора задача от Герасим Станчев

Обратно към всички решения

Към профила на Герасим Станчев

Резултати

  • 5 точки от тестове
  • 0 бонус точки
  • 5 точки общо
  • 20 успешни тест(а)
  • 4 неуспешни тест(а)

Код

require 'set'
class NumberSet
include Enumerable
def initialize
@set = Set.new
end
def [](filter)
new_set = NumberSet.new
filtered_sets = []
filter.filters.each { |filter| filtered_sets << filter.filter(@set.to_a) }
filtered_sets.each { |set| filtered_sets[0] = set & filtered_sets[0] }
filtered_sets[0].each { |number| new_set << number }
new_set
end
def <<(number)
return if @set.any? { |element| element == number }
@set << number
end
def empty?
@set.empty?
end
def size
@set.size
end
def each(&block)
@set.each(&block)
end
end
class SignFilter
include Enumerable
attr_accessor :subset_type
attr :filters
def initialize(subset_type)
@subset_type = subset_type
@filters = [self]
end
def filter(array)
case @subset_type
when :positive then array.select { |element| element > 0 }
when :non_positive then array.select { |element| element <= 0 }
when :negative then array.select { |element| element < 0 }
when :non_negative then array.select do |element|
element.class != Complex and element >= 0
end
end
end
def &(filter)
@filters << filter
self
end
def |(filter)
@filters << filter
self
end
def each(&block)
@array.each(&block)
end
end
class Filter
attr_accessor :block
attr :filters
def initialize(&block)
@block = block
@filters = [self]
end
def filter(array)
array.select { |element| @block.call(element.to_i) }
end
def &(filter)
@filters << filter
self
end
def |(filter)
@filters << filter
self
end
end
class TypeFilter
attr_accessor :type_filter
attr :filters
def initialize(type_filter)
@type_filter = type_filter
@filters = [self]
end
def filter(array)
case type_filter
when :integer then array.select { |element| element.is_a? Fixnum }
when :complex then array.select { |element| element.is_a? Complex }
when :real then array.select do |element|
element.class == Float or element.is_a? Rational
end
end
end
def &(filter)
@filters << filter
self
end
def |(filter)
@filters << filter
self
end
end

Лог от изпълнението

.................F.FFF..

Failures:

  1) NumberSet can combine two filters with "or" rule
     Failure/Error: expect(filtered_numbers.size).to eq expecting.size
       
       expected: 7
            got: 3
       
       (compared using ==)
     # /tmp/d20141028-18133-19cioml/spec.rb:180:in `can_filter'
     # /tmp/d20141028-18133-19cioml/spec.rb:99:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  2) NumberSet can combine multiple filters with "or" rule
     Failure/Error: expect(filtered_numbers.size).to eq expecting.size
       
       expected: 11
            got: 0
       
       (compared using ==)
     # /tmp/d20141028-18133-19cioml/spec.rb:180:in `can_filter'
     # /tmp/d20141028-18133-19cioml/spec.rb:119:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  3) NumberSet can combine multiple filters with "and" and "or" rules
     Failure/Error: expect(filtered_numbers.size).to eq expecting.size
       
       expected: 8
            got: 0
       
       (compared using ==)
     # /tmp/d20141028-18133-19cioml/spec.rb:180:in `can_filter'
     # /tmp/d20141028-18133-19cioml/spec.rb:129:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  4) NumberSet can combine multiple filters with "and", "or" and parenthesis
     Failure/Error: expect(filtered_numbers.size).to eq expecting.size
       
       expected: 4
            got: 1
       
       (compared using ==)
     # /tmp/d20141028-18133-19cioml/spec.rb:180:in `can_filter'
     # /tmp/d20141028-18133-19cioml/spec.rb:139:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.02436 seconds
24 examples, 4 failures

Failed examples:

rspec /tmp/d20141028-18133-19cioml/spec.rb:97 # NumberSet can combine two filters with "or" rule
rspec /tmp/d20141028-18133-19cioml/spec.rb:114 # NumberSet can combine multiple filters with "or" rule
rspec /tmp/d20141028-18133-19cioml/spec.rb:124 # NumberSet can combine multiple filters with "and" and "or" rules
rspec /tmp/d20141028-18133-19cioml/spec.rb:134 # NumberSet can combine multiple filters with "and", "or" and parenthesis

История (2 версии и 8 коментара)

Герасим обнови решението на 24.10.2014 23:47 (преди около 10 години)

+require('set')
+
+module SetMethods
+ def <<(number)
+ @set.each { |element| return if element == number }
+ @set << number
+ end
+
+ def empty?
+ @set.empty?
+ end
+
+ def size
+ @set.size
+ end
+
+ def each(&block)
+ @set.each(&block)
+ end
+end
+
+class NumberSet
+ include Enumerable
+ include SetMethods
+ attr_accessor :set
+
+ def initialize
+ @set = Set.new
+ end
+ def [](block)
+ new_set = NumberSet.new
+ filtered_sets = []
+ block.archie.each { |argument| filtered_sets << argument.filter(@set.to_a) }
+ block.procs.each { |argument| filtered_sets << argument.filter(@set.to_a) }
+ filtered_sets.each { |set| filtered_sets[0] = set & filtered_sets[0] }
+ return Set.new(filtered_sets[0])
+ end
+end
+
+class SignFilter
+ include Enumerable
+ attr_accessor :subset_type
+ attr :archie, :procs
+ def initialize(subset_type)
+ @subset_type = subset_type
+ @archie, @procs = [self], []
+ end
+
+ def filter(array)
+ case @subset_type
+ when :positive then array.select { |element| element > 0 }
+ when :non_positive then array.select { |element| element <= 0 }
+ when :negative then array.select { |element| element < 0 }
+ when :non_negative then array.select do
+ |element| element.class != Complex and element >= 0
+ end
+ end
+ end
+
+ def &(filter)
+ case filter
+ when TypeFilter then @archie << filter
+ when SignFilter then @archie << filter
+ when Filter then @procs << filter
+ end
+ self
+ end
+
+ def |(filter)
+ Set.new(@array | filter.array)
+ end
+ def each(&block)
+ @array.each(&block)
+ end
+end
+
+class Filter
+ attr_accessor :block
+
+ def initialize(subset_type)
+ @subset_type, @array = subset_type, []
+ end
+
+ def initialize(&block)
+ @block = block
+ end
+
+ def filter(array)
+ array.select { |element| @block.call(element.to_i) }
+ end
+
+ def &(filter)
+ #return self, filter
+ end
+end
+
+class TypeFilter
+ attr_accessor :type_filter
+
+ def initialize(type_filter)
+ @type_filter = type_filter
+ end
+
+ def filter(array)
+ case type_filter
+ when :integer then array.select { |element| element.class == Fixnum }
+ when :complex then array.select { |element| element.class == Complex }
+ when :real then array.select do
+ |element| element.class == Float or element.class == Rational
+ end
+ end
+ end
+end

Ето малко бележки:

  • Обикновено се изпускат скобите около require и се записва само така: require 'set'.
  • Не бих извеждал методи в SetMethods. Не го ползвам никъде другаде. Ако го ползвах, щях да си помисля, но в момента няма такъв use case. По-просто е да се намират директно в NumberSet.
  • Ред 5 ми прилича на нещо, което може би е вградено в Ruby. Проверил ли си? :)
  • Трябва да има празен ред между редове: 24 и 25, 29 и 30, 41 и 42, 42 и 43, 43 и 44.
  • Никъде не ползваш set=, който създаваш на ред 25. Setter методите рядко са добра идея, още повече ако не ги ползва човек. По-добре само attr_reader :set, ако го ползваш някъде.
  • Аргументът на NumberSet#[] не трябва да се казва block, защото не е block. filter е по-добро име.
  • Също така, този метод трябва да връща инстанция на NumberSet. Ти връщаш Set.
  • В този метод, по-добре ползвай Array#concat. Ще отпадне нуждата от ред 35.
  • Какво е argument на редове 33-34? Може би може да измислиш по-добро име, което казва какво е това нещо.
  • Защо не направиш метод each на филтрите, който да yield-ва само филтрирани елементи? Може да се ползва например така: filter.each(@set) { |element| filtered_elements << element }. Или, ако инстанцията на филтъра също е Enumerable, може да е дори filter.each(@set).to_a. Или просто метод #filter(@set), който връща списък от елементи.
  • Какво означава archie? Търсих в речник, чесах се зад врата, но не мога да разбера. Мисля, че може да се измисли по-добро име там.
  • Употребата на променливите archie и procs в NumberSet#[] изглежда като "изтичане" на имплементационни детайли навън. По-добре е да намериш друго решение. Мисля си и че би могъл да избегнеш това разделение, като по някакъв начин уеднаквиш вътрешния интерфейс на филтрите.
  • Когато имаш многоредов израз след then (редове 54-56), по-добре пропусни then и сложи израза на нов ред:

      when :non_negative
        array.select do |element|
          element.class != Complex and element >= 0
        end
    
  • Никога не свалай аргументите на блок на нов ред (визирам горния пример). |foo| трябва да идва веднага след do/{.

  • Проверявай дали обект е от даден клас с foo.is_a?(SomeClass).
  • Паралелното присвояване на ред 81 (и сходни) обикновено се избягва. На два реда си е достатъчно четимо.
  • Не съм сигурен дали array е добро вътрешно име за параметъра във Filter. Тези филтри са специализирани. Може би трябва да е numbers.

Мога да опонирам относно:

  • Ако имаш предвид include? oт Enumerable, не върши работа в случая, тъй като трябва да се сравнят оценъчно. Т.е. трябва да обходя с енумератор цялото множество. Мисля, че с any? ще изглежда по-чисто, но ако имаш по-добра идея, ще очаквам да ми я споделиш след като изтече срока за предаване. : -)

  • Oтносно разделението на различните attr-whatever - ок. Но не е описано в style guide-а на курса, а в bbatsov-ия писането на два един под друг не е посочено като проблем (даже е използвано в примери).

Относно archie - явява се slang за array. :D Тъй като не бях правил много итерации по него, съм го пропуснал. Благодаря за коментара - изчисти ми позицията относно няколко концепции.

Герасим обнови решението на 27.10.2014 16:59 (преди около 10 години)

-require('set')
+require 'set'
-module SetMethods
+class NumberSet
+ include Enumerable
+
+ def initialize
+ @set = Set.new
+ end
+
+ def [](filter)
+ new_set = NumberSet.new
+ filtered_sets = []
+ filter.filters.each { |filter| filtered_sets << filter.filter(@set.to_a) }
+ filtered_sets.each { |set| filtered_sets[0] = set & filtered_sets[0] }
+ filtered_sets[0].each { |number| new_set << number }
+ new_set
+ end
+
def <<(number)
- @set.each { |element| return if element == number }
+ return if @set.any? { |element| element == number }
@set << number
end
def empty?
@set.empty?
end
def size
@set.size
end
def each(&block)
@set.each(&block)
end
end
-class NumberSet
- include Enumerable
- include SetMethods
- attr_accessor :set
-
- def initialize
- @set = Set.new
- end
- def [](block)
- new_set = NumberSet.new
- filtered_sets = []
- block.archie.each { |argument| filtered_sets << argument.filter(@set.to_a) }
- block.procs.each { |argument| filtered_sets << argument.filter(@set.to_a) }
- filtered_sets.each { |set| filtered_sets[0] = set & filtered_sets[0] }
- return Set.new(filtered_sets[0])
- end
-end
-
class SignFilter
include Enumerable
+
attr_accessor :subset_type
- attr :archie, :procs
+
+ attr :filters
+
def initialize(subset_type)
@subset_type = subset_type
- @archie, @procs = [self], []
+ @filters = [self]
end
def filter(array)
case @subset_type
when :positive then array.select { |element| element > 0 }
when :non_positive then array.select { |element| element <= 0 }
when :negative then array.select { |element| element < 0 }
- when :non_negative then array.select do
- |element| element.class != Complex and element >= 0
+ when :non_negative then array.select do |element|
+ element.class != Complex and element >= 0
end
end
end
def &(filter)
- case filter
- when TypeFilter then @archie << filter
- when SignFilter then @archie << filter
- when Filter then @procs << filter
- end
+ @filters << filter
self
end
def |(filter)
- Set.new(@array | filter.array)
+ @filters << filter
+ self
end
+
def each(&block)
@array.each(&block)
end
end
class Filter
attr_accessor :block
- def initialize(subset_type)
- @subset_type, @array = subset_type, []
- end
+ attr :filters
def initialize(&block)
@block = block
+ @filters = [self]
end
def filter(array)
array.select { |element| @block.call(element.to_i) }
end
def &(filter)
- #return self, filter
+ @filters << filter
+ self
end
+
+ def |(filter)
+ @filters << filter
+ self
+ end
end
class TypeFilter
attr_accessor :type_filter
+ attr :filters
+
def initialize(type_filter)
@type_filter = type_filter
+ @filters = [self]
end
def filter(array)
case type_filter
- when :integer then array.select { |element| element.class == Fixnum }
- when :complex then array.select { |element| element.class == Complex }
- when :real then array.select do
- |element| element.class == Float or element.class == Rational
+ when :integer then array.select { |element| element.is_a? Fixnum }
+ when :complex then array.select { |element| element.is_a? Complex }
+ when :real then array.select do |element|
+ element.class == Float or element.is_a? Rational
end
end
+ end
+
+ def &(filter)
+ @filters << filter
+ self
+ end
+
+ def |(filter)
+ @filters << filter
+ self
end
end

Ех, как си знаех, че ако се интересуваш от това колко филтри навързваме ще има някакъв wtf момент. (:

Като предефинираш бинарни оператори, резултатът трябва да е резултатът от операцията. Представи си, че + върху числа беше имплементиран както ти си имплементирал Filter#& и Filter#|.

3 + 5 + 7 + 9

Щеше да връща 3 и после щеше да трябва да викаш 3.summed_numbers (или нещо от сорта), за да получиш [3, 5, 7, 9], което трябваше по някакъв магичен начин да сумираш. Също в момента имплементацията ти fail-ва, ако преизползваме филтър или просто сложим едни скоби:

filter_one & (filter_two | filter_three)

ще игнорира filter_three.

Не работят еднакво. Ако направиш Filter & Filter & ......... & Filter ще се приложат всички филтри. Но, ако направиш Filter | Filter ....whatever......, ще се приложат само първите два. Нито за момент не съм се опитвал да споря, че решението ми работи или че е най-доброто - просто това успях да генерирам като най-добро решение. Явно съм подходил косо към проблема и съм си представял двумерния случай.

Погледнах 2-3 много приятно изглеждащи решения още в 17:10. Схванах идеята, но явно още съм „зелен“ с блоковете и най-вече с предаването и употребата им в различни методи. : -)

@Митьо, връщам си думите за "include?". Явно съм се объркал при тестове.