Иван обнови решението на 30.10.2012 13:00 (преди около 12 години)
+class Song
+ def initialize(song)
+ @name = song[0]
+ @artist = song[1]
+ @album = song[2]
+ end
+
+ def appropriate?(criteria)
+ return self.conjuction?(criteria) if criteria.kind == 4
+ return self.union?(criteria) if criteria.kind == 5
+ return self.negation?(criteria) if criteria.kind >= 100
+ self.singles?(criteria)
+ end
+
+ def singles?(criteria)
+ return (name == criteria.instance[0]) if criteria.kind == 1
+ return (artist == criteria.instance[0]) if criteria.kind == 2
+ return (album == criteria.instance[0]) if criteria.kind == 3
+ end
+
+ def conjuction?(criteria)
+ new_array = [name, artist, album] - criteria.instance
+ return new_array.length <= 1
+ end
+
+ def union?(criteria)
+ new_array = criteria.instance - [name, artist, album]
+ return new_array.length <= 1
+ end
+
+ def negation?(criteria)
+ return (not (name == criteria.instance[0])) if criteria.kind == 100
+ return (not (artist == criteria.instance[0])) if criteria.kind == 200
+ return (not (album == criteria.instance[0])) if criteria.kind == 300
+ end
+
+ attr_reader :name, :artist, :album
+end
+
+class Collection
+ include Enumerable
+
+ def initialize
+ @songs = []
+ end
+
+ def each(&block)
+ @songs.each(&block)
+ end
+
+ def Collection.parse(text)
+ songs_collection = Collection.new
+ list = text.split("\n").select { | x | not x == "" }
+ list.each_slice(3) { | trio | songs_collection.songs << Song.new(trio) }
+ songs_collection
+ end
+
+ def names
+ names = []
+ each { | song | names << song.name }
+ names.uniq
+ end
+
+ def artists
+ artists = []
+ each { | song | artists << song.artist }
+ artists.uniq
+ end
+
+ def albums
+ albums = []
+ each { | song | albums << song.album }
+ albums.uniq
+ end
+
+ def filter(criteria)
+ sub_collection = Collection.new
+ sub_collection.songs = select { | song | song.appropriate? (criteria) }
+ sub_collection
+ end
+
+ def adjoin(collection)
+ sub_collection = Collection.new
+ sub_collection.songs = songs + collection.songs
+ sub_collection
+ end
+
+ attr_accessor :songs
+end
+
+class Criteria
+ include Enumerable
+
+ def initialize
+ @instance = []
+ @kind = 0
+ end
+
+ def each(&block)
+ @instance.each(&block)
+ end
+
+ def Criteria.name(song_name)
+ criteria = Criteria.new
+ criteria.instance[0] = song_name
+ criteria.kind = 1
+ criteria
+ end
+
+ def Criteria.artist(artist_name)
+ criteria = Criteria.new
+ criteria.instance[0] = artist_name
+ criteria.kind = 2
+ criteria
+ end
+
+ def Criteria.album(album_name)
+ criteria = Criteria.new
+ criteria.instance[0] = album_name
+ criteria.kind = 3
+ criteria
+ end
+
+ def &(other_criteria)
+ criteria = Criteria.new
+ criteria.instance = instance + other_criteria.instance
+ criteria.kind = 4
+ criteria
+ end
+
+ def |(other_criteria)
+ criteria = Criteria.new
+ criteria.instance = instance + other_criteria.instance
+ criteria.kind = 5
+ criteria
+ end
+
+ def !
+ criteria = Criteria.new
+ criteria.instance = instance
+ criteria.kind = 100 * kind
+ criteria
+ end
+
+ attr_accessor :instance, :kind
+end
Нали знаеш, че на конструктора на клас можеш да подаваш аргументи? :)
Кодът в Criteria
трябва да ти намирисва, понеже има страшно много повторение в инстанционните методи. Това повторение може да го минимизираш по различни начини, но поне добави някакви аргументи на конструктора и го направи така. Ето пример как може да изглежда метода ти Criteria.name
:
def self.name(song_name)
new song_name, 1
end
Забележи няколко неща:
- Ползвам
def self.name
за да дефинирам класов метод - Не викам
Criteria.new
, защото в този контекстself
иCriteria
са едно и също, а както знаем,new
е нормално извикване на метод и ако не посочим получател, този получател се подразбира катоself
. Т.е. следните три неща са едно и също в този контекст:Criteria.new
,self.new
иnew
. - Направо връщам създадената инстанция, след като съм подал аргументи на конструктора
Допълнителни бележки:
- Това с числата за
kind
вCriteria
е доста нетипично за Ruby, даже бих казал, че е нетипично и за езици като Java и C, където човек би си дефинирал поне един ENUM или някакви константи. Тук в Ruby символите са много подходящи за тази цел. - Мисля, че за това, за което го ползваш, полето
instance
наCriteria
е неподходящо именовано. - Дефинирай клас методи със
def self.method_name
, вместоdef ClassName.method_name
. - По конвенция
attr_accessor
неща обикновено се слагат в началото на класа, а не в дъното му. - Въпреки, че още не сме говорили в детайли за това, ред 3 до 5 могат да станат така:
@name, @artist, @album = song
. - Пропускай
self.
пред извикването на методи, това почти винаги е ненужно (напр. вSong#appropriate?
). -
new_array
не е добро име на променлива (заinstance
вече споменах). Други лоши имена:trio
- Пропускай
return
, когато се намира пред последно оценения израз в метод (напр. в методитеconjunction?
,union?
и т.н.) - На ред 78 не трябва да има интервал тук:
song.appropriate? (criteria)
, или трябва да махнеш скобите. - Не модифицирай полето
Collection#songs
отвън; така колекцията ти става mutable; направи я immutable и просто й подавай списъка с песни в конструктора. Целият ти код лесно може да се обърне в тази посока. Споменавали сме на лекции, че immutable обекти се предпочитат и сме казвали защо. - За методите
names
,artists
,albums
и прочее си припомни какво правиEnumerable#map
. Ако кодът ти остане в този вариант в тези методи, ще ти вземем точки.