Решение на Втора задача от Петър Костов

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

Към профила на Петър Костов

Резултати

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

Код

class Song
attr_reader :name, :artist, :album
def initialize(name, artist, album)
@name, @artist, @album = name, artist, album
end
def meets_criteria?
yield self
end
end
class Criteria
attr_reader :proc
def initialize(&block)
@proc = Proc.new &block
end
def self.name(text)
Criteria.new { |song| song.name == text }
end
def self.artist(text)
Criteria.new { |song| song.artist == text }
end
def self.album
Criteria.new { |song| song.album == text }
end
def |(criteria)
Criteria.new do |song|
@proc.call(song) or criteria.proc.call(song)
end
end
def &(criteria)
Criteria.new do |song|
@proc.call(song) and criteria.proc.call(song)
end
end
def !
Criteria.new { |song| not @proc.call(song) }
end
end
class Collection
include Enumerable
attr_reader :songs
def initialize(songs)
@songs = songs
end
def self.parse(text)
songs = []
text.lines.each_slice(4) do |sliced|
songs << Song.new(*sliced[0, 3].map(&:strip))
end
Collection.new(songs)
end
def each(&block)
@songs.each(&block)
end
def names
attribute_values(:name)
end
def artists
attribute_values(:artist)
end
def albums
attribute_values(:album)
end
def filter(criteria)
filtered = select { |song| song.meets_criteria?(&criteria.proc) }
Collection.new filtered
end
def adjoin(collection)
Collection.new (@songs + collection.songs).uniq
end
private
def attribute_values(attribute)
map { |song| song.public_send(attribute) }.uniq
end
end

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

.....FF....

Failures:

  1) Collection can be filtered by album
     Failure/Error: filtered = collection.filter Criteria.album('Live at Blues Alley')
     ArgumentError:
       wrong number of arguments (1 for 0)
     # /tmp/d20130203-23049-x3btml/solution.rb:28:in `album'
     # /tmp/d20130203-23049-x3btml/spec.rb:54:in `block (2 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  2) Collection can return an empty result
     Failure/Error: filtered = collection.filter Criteria.album('The Dark Side of the Moon')
     ArgumentError:
       wrong number of arguments (1 for 0)
     # /tmp/d20130203-23049-x3btml/solution.rb:28:in `album'
     # /tmp/d20130203-23049-x3btml/spec.rb:59:in `block (2 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.01056 seconds
11 examples, 2 failures

Failed examples:

rspec /tmp/d20130203-23049-x3btml/spec.rb:53 # Collection can be filtered by album
rspec /tmp/d20130203-23049-x3btml/spec.rb:58 # Collection can return an empty result

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

Петър обнови решението на 26.10.2012 22:32 (преди над 11 години)

+class Song
+ attr_reader :name, :artist, :album
+
+ def initialize(song_array)
+ @name, @artist, @album = song_array[0], song_array[1], song_array[2]
+ end
+
+ def meets_criteria?(criteria)
+ eval(criteria.to_s)
+ end
+end
+
+class Criteria
+ attr_reader :collection
+
+ def initialize(criteria)
+ @collection = [criteria]
+ end
+
+ def self.name(text)
+ Criteria.new [:name, :==, "'"+text+"'"]
+ end
+
+ def self.artist(text)
+ Criteria.new [:artist, :==, "'"+text+"'"]
+ end
+
+ def self.album(text)
+ Criteria.new [:album, :==, "'"+text+"'"]
+ end
+
+ def |(criteria)
+ @collection << [:or, criteria.collection]
+ Criteria.new(@collection.flatten)
+ end
+
+ def &(criteria)
+ @collection << [:and, criteria.collection]
+ Criteria.new(@collection.flatten)
+ end
+
+ def !()
+ Criteria.new(@collection.insert(0, :not))
+ end
+
+ def to_s
+ @collection.join(" ")
+ end
+
+end
+
+class Collection
+ include Enumerable
+
+ attr_reader :songs
+
+ def initialize(songs)
+ @songs = songs
+ end
+
+ def self.parse(text)
+ songs = []
+ text.lines.each_slice(4) do |sliced|
+ songs << Song.new(sliced[0, 3].map(&:strip))
+ end
+ Collection.new(songs)
+ end
+
+ def each(&block)
+ @songs.each(&block)
+ end
+
+ def names
+ map { |song| song.name }.uniq
+ end
+
+ def artists
+ map { |song| song.artist }.uniq
+ end
+
+ def albums
+ map { |song| song.album }.uniq
+ end
+
+ def filter(criteria)
+ Collection.new(select { |song| song.meets_criteria?(criteria) })
+ end
+
+ def adjoin(collection)
+ Collection.new (@songs + collection.songs).uniq
+ end
+
+end

Предавам с цел да въпрос. :) Обосновано ли е в случая използването на eval? Четох из нета, че eval се ползва само ако няма друг вариант, следователно май не. Но все пак да попитам. :)

Чист eval е зло :) Ruby предоставя няколко други такива, които се ползват - instance_eval и {class|module}_eval, но за тях по-нататък.

Като гледам кода, това което правиш с eval може да го направиш и с блокове. След като ги запазиш може да ги извикаш с block.call, block.() или block[]. Последното не го ползвай :)

Отделно може да изпуснеш, скобите при def !(). Освен това решението ти изглежда добре, но оправи евала.

Благодаря за коментара. :) Като цяло се колебаех за eval, защото в момента става много просто решението. Методите са картички и четими според мен. С блокове или ламбди едва ли ще мога да постигна много четим код. Най-вероятно ще сменя схемата като цяло.

Петър обнови решението на 27.10.2012 13:10 (преди над 11 години)

class Song
attr_reader :name, :artist, :album
def initialize(song_array)
@name, @artist, @album = song_array[0], song_array[1], song_array[2]
end
def meets_criteria?(criteria)
- eval(criteria.to_s)
+ criteria.collection.inject(false) do |result, entity|
+ result = evaluate_entity(entity, result)
+ end
end
+
+ private
+ def evaluate_entity(entity, result)
+ result = (result or entity_value(entity)) if entity.first == :or
+ result = (result and entity_value(entity)) if entity.first == :and
+ result = entity_value(entity) if entity.first == :none
+ result
+ end
+
+ def entity_value(entity)
+ if entity.last == :not
+ not send(entity[1]) == entity[2]
+ else
+ send(entity[1]) == entity[2]
+ end
+ end
end
class Criteria
attr_reader :collection
def initialize(criteria)
- @collection = [criteria]
+ @collection = criteria
end
def self.name(text)
- Criteria.new [:name, :==, "'"+text+"'"]
+ Criteria.new [[:none, :name, text]]
end
def self.artist(text)
- Criteria.new [:artist, :==, "'"+text+"'"]
+ Criteria.new [[:none, :artist, text]]
end
def self.album(text)
- Criteria.new [:album, :==, "'"+text+"'"]
+ Criteria.new [[:none, :album, text]]
end
def |(criteria)
- @collection << [:or, criteria.collection]
- Criteria.new(@collection.flatten)
+ criteria.collection[0][0] = :or
+ @collection << criteria.collection.flatten
+ Criteria.new(@collection)
end
def &(criteria)
- @collection << [:and, criteria.collection]
- Criteria.new(@collection.flatten)
+ criteria.collection[0][0] = :and
+ @collection << criteria.collection.flatten
+ Criteria.new(@collection)
end
- def !()
- Criteria.new(@collection.insert(0, :not))
+ def !
+ @collection[0] << :not
+ Criteria.new(@collection)
end
-
- def to_s
- @collection.join(" ")
- end
-
end
class Collection
include Enumerable
attr_reader :songs
def initialize(songs)
@songs = songs
end
def self.parse(text)
songs = []
text.lines.each_slice(4) do |sliced|
songs << Song.new(sliced[0, 3].map(&:strip))
end
Collection.new(songs)
end
def each(&block)
@songs.each(&block)
end
def names
map { |song| song.name }.uniq
end
def artists
map { |song| song.artist }.uniq
end
def albums
map { |song| song.album }.uniq
end
def filter(criteria)
Collection.new(select { |song| song.meets_criteria?(criteria) })
end
def adjoin(collection)
Collection.new (@songs + collection.songs).uniq
end
-
end

Петър обнови решението на 27.10.2012 21:47 (преди над 11 години)

class Song
attr_reader :name, :artist, :album
def initialize(song_array)
@name, @artist, @album = song_array[0], song_array[1], song_array[2]
end
def meets_criteria?(criteria)
- criteria.collection.inject(false) do |result, entity|
- result = evaluate_entity(entity, result)
- end
+ criteria.proc.call(self)
end
-
- private
- def evaluate_entity(entity, result)
- result = (result or entity_value(entity)) if entity.first == :or
- result = (result and entity_value(entity)) if entity.first == :and
- result = entity_value(entity) if entity.first == :none
- result
- end
-
- def entity_value(entity)
- if entity.last == :not
- not send(entity[1]) == entity[2]
- else
- send(entity[1]) == entity[2]
- end
- end
end
class Criteria
- attr_reader :collection
+ attr_reader :proc
def initialize(criteria)
- @collection = criteria
+ @proc = criteria
end
def self.name(text)
- Criteria.new [[:none, :name, text]]
+ Criteria.new(Proc.new { |song| song.name == text })
end
def self.artist(text)
- Criteria.new [[:none, :artist, text]]
+ Criteria.new(Proc.new { |song| song.artist == text })
end
- def self.album(text)
- Criteria.new [[:none, :album, text]]
+ def self.album
+ Criteria.new(Proc.new { |song| song.album == text })
end
def |(criteria)
- criteria.collection[0][0] = :or
- @collection << criteria.collection.flatten
- Criteria.new(@collection)
+ new_proc = Proc.new do |song|
+ @proc.call(song) or criteria.proc.call(song)
+ end
+ Criteria.new new_proc
end
def &(criteria)
- criteria.collection[0][0] = :and
- @collection << criteria.collection.flatten
- Criteria.new(@collection)
+ new_proc = Proc.new do |song|
+ @proc.call(song) and criteria.proc.call(song)
+ end
+ Criteria.new new_proc
end
def !
- @collection[0] << :not
- Criteria.new(@collection)
+ Criteria.new Proc.new { |song| not @proc.call(song) }
end
end
class Collection
include Enumerable
attr_reader :songs
def initialize(songs)
@songs = songs
end
def self.parse(text)
songs = []
text.lines.each_slice(4) do |sliced|
songs << Song.new(sliced[0, 3].map(&:strip))
end
Collection.new(songs)
end
def each(&block)
@songs.each(&block)
end
def names
map { |song| song.name }.uniq
end
def artists
map { |song| song.artist }.uniq
end
def albums
map { |song| song.album }.uniq
end
def filter(criteria)
- Collection.new(select { |song| song.meets_criteria?(criteria) })
+ filtered = select { |song| song.meets_criteria?(criteria) }
+ Collection.new filtered
end
def adjoin(collection)
Collection.new (@songs + collection.songs).uniq
end
end

Две неща.

Първо, тъпо е конструктора на Song да взема масив. Дай ми три аргумента и оправи останалия код.

Второ, map { |song| song.xxxx }.uniq се повтаря три пъти. Някакъв начин да го изнесеш?

Трето, погледни форумите за още един hint.

Петър обнови решението на 27.10.2012 22:54 (преди над 11 години)

class Song
attr_reader :name, :artist, :album
- def initialize(song_array)
- @name, @artist, @album = song_array[0], song_array[1], song_array[2]
+ def initialize(name, artist, album)
+ @name, @artist, @album = name, artist, album
end
- def meets_criteria?(criteria)
- criteria.proc.call(self)
+ def meets_criteria?
+ yield self
end
end
class Criteria
attr_reader :proc
def initialize(criteria)
@proc = criteria
end
def self.name(text)
Criteria.new(Proc.new { |song| song.name == text })
end
def self.artist(text)
Criteria.new(Proc.new { |song| song.artist == text })
end
def self.album
Criteria.new(Proc.new { |song| song.album == text })
end
def |(criteria)
new_proc = Proc.new do |song|
@proc.call(song) or criteria.proc.call(song)
end
Criteria.new new_proc
end
def &(criteria)
new_proc = Proc.new do |song|
@proc.call(song) and criteria.proc.call(song)
end
Criteria.new new_proc
end
def !
Criteria.new Proc.new { |song| not @proc.call(song) }
end
end
class Collection
include Enumerable
attr_reader :songs
def initialize(songs)
@songs = songs
end
def self.parse(text)
songs = []
text.lines.each_slice(4) do |sliced|
- songs << Song.new(sliced[0, 3].map(&:strip))
+ songs << Song.new(*sliced[0, 3].map(&:strip))
end
Collection.new(songs)
end
def each(&block)
@songs.each(&block)
end
def names
- map { |song| song.name }.uniq
+ attributes(:name)
end
def artists
- map { |song| song.artist }.uniq
+ attributes(:artist)
end
def albums
- map { |song| song.album }.uniq
+ attributes(:album)
end
def filter(criteria)
- filtered = select { |song| song.meets_criteria?(criteria) }
+ filtered = select { |song| song.meets_criteria?(&criteria.proc) }
Collection.new filtered
end
def adjoin(collection)
Collection.new (@songs + collection.songs).uniq
+ end
+
+ private
+
+ def attributes(attribute)
+ map { |song| song.public_send(attribute) }.uniq
end
end

Петър обнови решението на 27.10.2012 23:01 (преди над 11 години)

class Song
attr_reader :name, :artist, :album
def initialize(name, artist, album)
@name, @artist, @album = name, artist, album
end
def meets_criteria?
yield self
end
end
class Criteria
attr_reader :proc
- def initialize(criteria)
- @proc = criteria
+ def initialize(&block)
+ @proc = Proc.new &block
end
def self.name(text)
- Criteria.new(Proc.new { |song| song.name == text })
+ Criteria.new { |song| song.name == text }
end
def self.artist(text)
- Criteria.new(Proc.new { |song| song.artist == text })
+ Criteria.new { |song| song.artist == text }
end
def self.album
- Criteria.new(Proc.new { |song| song.album == text })
+ Criteria.new { |song| song.album == text }
end
def |(criteria)
- new_proc = Proc.new do |song|
+ Criteria.new do |song|
@proc.call(song) or criteria.proc.call(song)
end
- Criteria.new new_proc
end
def &(criteria)
- new_proc = Proc.new do |song|
+ Criteria.new do |song|
@proc.call(song) and criteria.proc.call(song)
end
- Criteria.new new_proc
end
def !
- Criteria.new Proc.new { |song| not @proc.call(song) }
+ Criteria.new { |song| not @proc.call(song) }
end
end
class Collection
include Enumerable
attr_reader :songs
def initialize(songs)
@songs = songs
end
def self.parse(text)
songs = []
text.lines.each_slice(4) do |sliced|
songs << Song.new(*sliced[0, 3].map(&:strip))
end
Collection.new(songs)
end
def each(&block)
@songs.each(&block)
end
def names
attributes(:name)
end
def artists
attributes(:artist)
end
def albums
attributes(:album)
end
def filter(criteria)
filtered = select { |song| song.meets_criteria?(&criteria.proc) }
Collection.new filtered
end
def adjoin(collection)
Collection.new (@songs + collection.songs).uniq
end
private
def attributes(attribute)
map { |song| song.public_send(attribute) }.uniq
end
end

Добре, супер близо си до моето решение.

Само един коментар - защо метода се казва attributes? Той не връща атрибути. Връща стойностите на даден атрибут в цялата колекция. Имаш две алтернативи - или да формулираш "даден атрибут за всички елементи" или да намериш дума, която значи това.

Петър обнови решението на 27.10.2012 23:14 (преди над 11 години)

class Song
attr_reader :name, :artist, :album
def initialize(name, artist, album)
@name, @artist, @album = name, artist, album
end
def meets_criteria?
yield self
end
end
class Criteria
attr_reader :proc
def initialize(&block)
@proc = Proc.new &block
end
def self.name(text)
Criteria.new { |song| song.name == text }
end
def self.artist(text)
Criteria.new { |song| song.artist == text }
end
def self.album
Criteria.new { |song| song.album == text }
end
def |(criteria)
Criteria.new do |song|
@proc.call(song) or criteria.proc.call(song)
end
end
def &(criteria)
Criteria.new do |song|
@proc.call(song) and criteria.proc.call(song)
end
end
def !
Criteria.new { |song| not @proc.call(song) }
end
end
class Collection
include Enumerable
attr_reader :songs
def initialize(songs)
@songs = songs
end
def self.parse(text)
songs = []
text.lines.each_slice(4) do |sliced|
songs << Song.new(*sliced[0, 3].map(&:strip))
end
Collection.new(songs)
end
def each(&block)
@songs.each(&block)
end
def names
- attributes(:name)
+ attribute_values(:name)
end
def artists
- attributes(:artist)
+ attribute_values(:artist)
end
def albums
- attributes(:album)
+ attribute_values(:album)
end
def filter(criteria)
filtered = select { |song| song.meets_criteria?(&criteria.proc) }
Collection.new filtered
end
def adjoin(collection)
Collection.new (@songs + collection.songs).uniq
end
private
- def attributes(attribute)
+ def attribute_values(attribute)
map { |song| song.public_send(attribute) }.uniq
end
end