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

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

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

Резултати

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

Код

class Song
attr_reader :name, :artist, :genre, :subgenre, :tags
def initialize(attributes={})
attributes.each do |attribute, value|
instance_variable_set "@#{ attribute.to_s}", value
end
end
def matches?(criteria)
filters = {
:name => @name == criteria[:name],
:artist => @artist == criteria[:artist],
:tags => (matches_tags? criteria[:tags]),
:filter => (criteria[:filter] and criteria[:filter].(self)),
}
criteria.map { |key, value| filters[key] }.all?
end
private
def matches_tags?(tags)
tags = Array(tags)
wanted = tags.reject { |x| x.end_with? "!" }
unwanted = tags.select { |x| x.end_with? "!" }
unwanted.map! { |tag| tag.delete("!") }
if wanted.all? { |tag| @tags.include? tag }
return unwanted.none? { |tag| @tags.include? tag }
end
false
end
end
class Collection
def initialize(songs_string, extra_tags={})
@songs = songs_string.each_line.map do |song_string|
Song.new make_song_attributes(song_string.strip, extra_tags)
end
end
def find(criteria={})
@songs.select { |song| song.matches? criteria }
end
private
def make_song_attributes(song_string, extra_tags)
attributes = parse song_string
# attributes == [name, artist, genre, subgenre, tags]
extra_tags.default = []
{
:name => attributes[0],
:artist => attributes[1],
:genre => attributes[2],
:subgenre => attributes[3],
:tags => (attributes[4].concat extra_tags[attributes[1]]),
}
end
def parse(song_string)
attributes = song_string.split /\s*\.\s*/
genres = attributes[2].split /\s*,\s*/
tags = attributes.fetch(3, '').split /\s*,\s*/
attributes[2] = genres[0] # Set Genre
attributes[3] = genres[1] # Set Subgenre, will set nil if none
attributes[4] = tags.concat genres.map(&:downcase)
attributes
end
end

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

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

Finished in 0.54787 seconds
12 examples, 0 failures

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

Пламен обнови решението на 26.10.2011 19:57 (преди над 12 години)

+class Song
+ attr_reader :name, :artist, :genre, :subgenre, :tags
+
+ def initialize(attributes={})
+ attributes.each do |attribute, value|
+ instance_variable_set "@#{ attribute.to_s}", value
+ end
+ end
+
+ def matches?(criteria)
+ filters = {
+ :name => @name == criteria[:name],
+ :artist => @artist == criteria[:artist],
+ :tags => (matches_tags? criteria[:tags]),
+ :filter => (criteria[:filter] and criteria[:filter].(self)),
+ }
+ criteria.map { |key, value| filters[key] }.all?
+ end
+
+ private
+
+ def matches_tags?(tags)
+ tags = Array(tags)
+ wanted = tags.reject { |x| x.end_with? "!" }
+ unwanted = tags.select { |x| x.end_with? "!" }
+ unwanted.map! { |tag| tag.delete("!") }
+ if wanted.all? { |tag| @tags.include? tag }
+ return unwanted.none? { |tag| @tags.include? tag }
+ end
+ false
+ end
+end
+
+class Collection
+ def initialize(songs_string, extra_tags={})
+ @songs = songs_string.each_line.map do |song_string|
+ Song.new make_song_attributes(song_string.strip, extra_tags)
+ end
+ end
+
+ def find(criteria={})
+ @songs.select { |song| song.matches? criteria }
+ end
+
+ private
+
+ def make_song_attributes(song_string, extra_tags)
+ attributes = parse song_string
+ # attributes == [name, artist, genre, subgenre, tags]
+ extra_tags.default = []
+ {
+ :name => attributes[0],
+ :artist => attributes[1],
+ :genre => attributes[2],
+ :subgenre => attributes[3],
+ :tags => (attributes[4].concat extra_tags[attributes[1]]),
+ }
+ end
+
+ def parse(song_string)
+ attributes = song_string.split /\s*\.\s*/
+ genres = attributes[2].split /\s*,\s*/
+ tags = attributes.fetch(3, '').split /\s*,\s*/
+ attributes[2] = genres[0] # Set Genre
+ attributes[3] = genres[1] # Set Subgenre, will set nil if none
+ attributes[4] = tags.concat genres.map(&:downcase)
+ attributes
+ end
+end
  • Конструктора ти на Song е забавен, но вместо да правиш instance_variable_set, по-добре да минаваш с send "#{attribute}=", value. Трябва да ползваш attr_accessor. Браво, че си се сетил.
  • Вместо songs_string.each_line.map трябваше да напишеш songs_string.lines.map. По-добре се чете.
  • В matches_tags? трябваше да напишеш wanted.all? {} and unwanted.none? {}. Това с болевата логика е и return-а в if-а е много неинтуитивно.
  • Хубаво си съкратил matches?, но не ми харесва как прави винаги тези проверки. По-добре е да не сравняваш име, ако не ти е подадено :name.

Иначе, хубаво, кратко и елеганто. Браво. Давам ти две бонус точки. Третата не ти я давам заради if all?; return none?; end; false сегмента.

Това с if all?; return none?; end; false го направих, защото ми се струваше, че иначе редът става много дълъг. Първоначално беше така, както трябваше да се напише :)

А за да не се проверява за име, когато нямаме зададено :name това :name => criteria[:name] && @name == criteria[:name] удачно ли е?