Решение на Втора задача от Екатерина Горанова

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

Към профила на Екатерина Горанова

Резултати

  • 6 точки от тестове
  • 1 бонус точка
  • 7 точки общо
  • 24 успешни тест(а)
  • 0 неуспешни тест(а)

Код

class NumberSet
include Enumerable
def initialize
@set = []
end
def <<(new_number)
@set << new_number unless @set.include? new_number
end
def size
@set.size
end
def empty?
@set.empty?
end
def [](filter)
@set.each_with_object(NumberSet.new) do |number, new_set|
new_set << number if filter.satisfied_by? number
end
end
def each(&block)
@set.each(&block)
end
end
class Filter
def initialize(&block)
@filter = block
end
def satisfied_by?(number)
@filter.call number
end
def &(other)
Filter.new { |number| satisfied_by? number and other.satisfied_by? number }
end
def |(other)
Filter.new { |number| satisfied_by? number or other.satisfied_by? number }
end
end
class TypeFilter < Filter
def initialize(type)
case type
when :integer then super() { |number| number.integer? }
when :complex then super() { |number| not number.real? }
when :real then super() { |number| number.real? and not number.integer? }
end
end
end
class SignFilter < Filter
def initialize(sign)
case sign
when :positive then super() { |number| number > 0 }
when :negative then super() { |number| number < 0 }
when :non_positive then super() { |number| number <= 0 }
when :non_negative then super() { |number| number >= 0 }
end
end
end

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

........................

Finished in 0.0225 seconds
24 examples, 0 failures

История (4 версии и 6 коментара)

Екатерина обнови решението на 21.10.2014 21:11 (преди над 9 години)

+class Array
+ def contains?(number)
+ self.map { |element| element.to_f }.include? number
+ end
+end
+
+class NumberSet
+ def initialize
+ @set = []
+ end
+
+ def <<(n)
+ @set << n unless @set.contains? n
+ end
+
+ def size
+ @set.size
+ end
+
+ def empty?
+ size == 0 ? true : false
+ end
+
+ def [](filter)
+ @set.select { |n| filter.filter_by.call n }
+ end
+end
+
+class Filter
+ def initialize(&block)
+ @filter_by = block
+ end
+
+ def filter_by
+ @filter_by
+ end
+
+ def &(other)
+ Filter.new { |n| @filter_by.call n and other.filter_by.call n }
+ end
+
+ def |(other)
+ Filter.new { |n| @filter_by.call n or other.filter_by.call n }
+ end
+end
+
+class TypeFilter < Filter
+ def initialize(filter_by)
+ case filter_by
+ when :integer then @filter_by = ->(n) { n.integer? }
+ when :real then @filter_by = ->(n) { n.real? and not n.integer? }
+ when :complex then @filter_by = ->(n) { not n.real? }
+ end
+ end
+end
+
+class SignFilter < Filter
+ def initialize(filter_by)
+ case filter_by
+ when :positive then @filter_by = ->(n) { n > 0 }
+ when :negative then @filter_by = ->(n) { n < 0 }
+ when :non_positive then @filter_by = ->(n) { n <= 0 }
+ when :non_negative then @filter_by = ->(n) { n >= 0 }
+ end
+ end
+end

Здрасти,

Няколко забележки по иначе добрия ти код:

  • Гледай да не monkey patch-ваш класове, особено ако са толкова common колкото Array. contains?(array, number) в рамките на твоя клас би бил по-добър вариант от колкото да промениш всички Array обекти във Вселената. Необходим ли ти е този метод изобщо и в каква ситуация?
  • size == 0 ? true : false можеш да го запишеш още като size == 0 ? true ? true ? true : false : false : false или пък по-простото size == 0 :) Все пак == връща булева стойност. P.S. Array има метод empty?, така че можеш да върнеш направо @set.empty?. Според мен ще е по-просто.

Има две неща, които не ми харесват във филтрите ти:

  • Пазиш анонимна функция в класа, с която ще извършваш оценката и имаш метод, който я показва на външния свят. Визирам Filter#filter_by. Какво мислиш за варианта да скриеш начина, по който става тази оценка (да скриеш ламбдата) и да имаш метод, който приема число и връща true/false в зависимост от това дали удовлетворява филтъра?
  • Второто нещо, което не ми харесва е начина, по който се присвояват анонимните функции в класовете TypeFilter и SignFilter. Полето @filter_by ми се струва малко магическо гледайки само тези два класа. Чудя се дали би бил по-добър варианта да викаш super с блок. Можеш да пробваш :)

Малко храна за размисъл. Готино решение иначе, продължавай така!

Екатерина обнови решението на 22.10.2014 11:42 (преди над 9 години)

-class Array
- def contains?(number)
- self.map { |element| element.to_f }.include? number
- end
-end
-
class NumberSet
def initialize
@set = []
end
def <<(n)
- @set << n unless @set.contains? n
+ @set << n unless contains? n
end
def size
@set.size
end
def empty?
- size == 0 ? true : false
+ @set.empty?
end
def [](filter)
- @set.select { |n| filter.filter_by.call n }
+ @set.select { |n| filter.apply_filter n }
end
+
+ def contains?(number)
+ @set.map { |element| element.to_f }.include? number
+ end
end
class Filter
def initialize(&block)
- @filter_by = block
+ @filter = block
end
- def filter_by
- @filter_by
+ def apply_filter(n)
+ @filter.call n
end
def &(other)
- Filter.new { |n| @filter_by.call n and other.filter_by.call n }
+ Filter.new { |n| apply_filter n and other.apply_filter n }
end
def |(other)
- Filter.new { |n| @filter_by.call n or other.filter_by.call n }
+ Filter.new { |n| apply_filter n or other.apply_filter n }
end
end
class TypeFilter < Filter
def initialize(filter_by)
case filter_by
- when :integer then @filter_by = ->(n) { n.integer? }
- when :real then @filter_by = ->(n) { n.real? and not n.integer? }
- when :complex then @filter_by = ->(n) { not n.real? }
+ when :integer then super & lambda { |n| n.integer? }
+ when :real then super & lambda { |n| n.real? and not n.integer? }
+ when :complex then super & lambda { |n| not n.real? }
end
end
end
class SignFilter < Filter
def initialize(filter_by)
case filter_by
- when :positive then @filter_by = ->(n) { n > 0 }
- when :negative then @filter_by = ->(n) { n < 0 }
- when :non_positive then @filter_by = ->(n) { n <= 0 }
- when :non_negative then @filter_by = ->(n) { n >= 0 }
+ when :positive then super & lambda { |n| n > 0 }
+ when :negative then super & lambda { |n| n < 0 }
+ when :non_positive then super & lambda { |n| n <= 0 }
+ when :non_negative then super & lambda { |n| n >= 0 }
end
end
end

Не е точна наука. Трудно ще ти дам алгоритъм. По-скоро някакви неща, които аз следвам. Може някое от тях да ти се стори полезно.

Разделяй кода си на малки, ясно дефинирани части. Така ще ти е лесно да им даваш имена. Не можеш да дадеш смислено име на метод, който прави 5 различни неща, в зависимост от 4 параметъра.

Имената трябва да са ясни и описателни:

  • number е по-добре от n
  • index е по-добре от i
  • text / string е по-добре от str.

Позволявам си да ползвам кратки имена от сорта на n само когато scope-а на името е изключително малък. Например в блок, ако говорим за Ruby.

Имената трябва да са максимално близо до домейна на проблема, който решаваме. Кода става по-разбираем. Говори за неща, които очакваме да видим (поради естеството на проблема), а не понятия измислени от различните разработчици писали го. В контекста на NumberSet - number е по-добре от item / element. Като цяло трябва да се избягват "абстрактните" имена по възможност.

Имената трябва задължително да са на английски език и да са правилно подбрани. Когато се съмнявам какво име да дам отварям речник за да търся подходящи думи. Много често се хващам, че търся синоними в Thesaurus примерно, a Google Translate ми е постоянно отворен някъде из tab-овете.

Примерно, в контекста на твоето решение следните неща ми се набиват на очи:

  • В конструктура на TypeFilter подаваме очаквания тип. Защо не си кръстиш параметъра type / number_type вместо filter_by. Така и case statement-а ще има повече смисъл. Същото за SignFilter.
  • Помисли каква е задачата на Filter#apply_filter. Той взима число и връща true / false в зависимост от това дали числото удовлетворява филтъра. Казвали сме, че такива методи в Ruby наименоваме с нещо, което завършва на ?. Това може да ти подскаже, че може би има по-добро име от apply_filter. Какво ще кажеш за нещо от сорта на Filter#matches? или Filter#satisfies?? Или пък Filter#satisfied_by?? Каква е разликата между последните две? :)

Нещо такова :)

Екатерина обнови решението на 22.10.2014 15:30 (преди над 9 години)

class NumberSet
def initialize
@set = []
end
- def <<(n)
- @set << n unless contains? n
+ def <<(number)
+ @set << number unless contains? number
end
def size
@set.size
end
def empty?
@set.empty?
end
def [](filter)
- @set.select { |n| filter.apply_filter n }
+ @set.select { |number| filter.satisfied_by? number }
end
def contains?(number)
@set.map { |element| element.to_f }.include? number
end
end
class Filter
def initialize(&block)
@filter = block
end
- def apply_filter(n)
- @filter.call n
+ def satisfied_by?(number)
+ @filter.call number
end
def &(other)
- Filter.new { |n| apply_filter n and other.apply_filter n }
+ Filter.new { |number| satisfied_by? number and other.satisfied_by? number }
end
def |(other)
- Filter.new { |n| apply_filter n or other.apply_filter n }
+ Filter.new { |number| satisfied_by? number or other.satisfied_by? number }
end
end
class TypeFilter < Filter
- def initialize(filter_by)
- case filter_by
- when :integer then super & lambda { |n| n.integer? }
- when :real then super & lambda { |n| n.real? and not n.integer? }
- when :complex then super & lambda { |n| not n.real? }
+ def initialize(type)
+ case type
+ when :integer then super &(->(number) { number.integer? })
+ when :complex then super &(->(number) { not number.real? })
+ when :real
+ super &(->(number) { number.real? and not number.integer? })
end
end
end
class SignFilter < Filter
- def initialize(filter_by)
- case filter_by
- when :positive then super & lambda { |n| n > 0 }
- when :negative then super & lambda { |n| n < 0 }
- when :non_positive then super & lambda { |n| n <= 0 }
- when :non_negative then super & lambda { |n| n >= 0 }
+ def initialize(sign)
+ case sign
+ when :positive then super &(->(number) { number > 0 })
+ when :negative then super &(->(number) { number < 0 })
+ when :non_positive then super &(->(number) { number <= 0 })
+ when :non_negative then super &(->(number) { number >= 0 })
end
end
end

Много добре.

Сещам се за още три неща:

  • super &(->(number) { number > 0 }) бих го записал така super() { |number| number > 0 }. Подаваме блок на горния клас използвайки синтаксиса за блок.
  • NumberSet#[] трябва да връща NumberSet обект след филтрирането. В момента връщаш Array обект.
  • Защо ти се налага да правиш тази трансформация @set.map { |element| element.to_f }? Не може ли просто @set.include? number?

Екатерина обнови решението на 25.10.2014 15:45 (преди над 9 години)

class NumberSet
+ include Enumerable
+
def initialize
@set = []
end
- def <<(number)
- @set << number unless contains? number
+ def <<(new_number)
+ @set << new_number unless @set.include? new_number
end
def size
@set.size
end
def empty?
@set.empty?
end
def [](filter)
- @set.select { |number| filter.satisfied_by? number }
+ @set.each_with_object(NumberSet.new) do |number, new_set|
+ new_set << number if filter.satisfied_by? number
+ end
end
- def contains?(number)
- @set.map { |element| element.to_f }.include? number
+ def each(&block)
+ @set.each(&block)
end
end
class Filter
def initialize(&block)
@filter = block
end
def satisfied_by?(number)
@filter.call number
end
def &(other)
Filter.new { |number| satisfied_by? number and other.satisfied_by? number }
end
def |(other)
Filter.new { |number| satisfied_by? number or other.satisfied_by? number }
end
end
class TypeFilter < Filter
def initialize(type)
case type
- when :integer then super &(->(number) { number.integer? })
- when :complex then super &(->(number) { not number.real? })
- when :real
- super &(->(number) { number.real? and not number.integer? })
+ when :integer then super() { |number| number.integer? }
+ when :complex then super() { |number| not number.real? }
+ when :real then super() { |number| number.real? and not number.integer? }
end
end
end
class SignFilter < Filter
def initialize(sign)
case sign
- when :positive then super &(->(number) { number > 0 })
- when :negative then super &(->(number) { number < 0 })
- when :non_positive then super &(->(number) { number <= 0 })
- when :non_negative then super &(->(number) { number >= 0 })
+ when :positive then super() { |number| number > 0 }
+ when :negative then super() { |number| number < 0 }
+ when :non_positive then super() { |number| number <= 0 }
+ when :non_negative then super() { |number| number >= 0 }
end
end
end