Георги обнови решението на 30.10.2012 00:12 (преди около 12 години)
+
+class Collection
+ include Enumerable
+
+ attr_accessor :songs
+
+ def initialize(songs = [])
+ @songs = songs
+ end
+
+ def self.parse(text)
+ text.split("\n\n").inject(Collection.new) do |collection, song|
+ name, artist, album = song.split("\n")
+ collection.songs << Song.new(name, artist, album)
+ collection
+ end
+ end
+
+ def names
+ songs.map { |song| song.name }.uniq
+ end
+
+ def artists
+ songs.map { |song| song.artist }.uniq
+ end
+
+ def albums
+ songs.map { |song| song.album }.uniq
+ end
+
+ def filter(criteria)
+ collection = songs.select { |song| song.match?(criteria) }
+ Collection.new(collection)
+ end
+
+ def adjoin(collection)
+ Collection.new(songs.concat(collection.songs).uniq)
+ end
+
+ def each
+ songs.each { |song| yield song }
+ end
+end
+
+class Criterion
+ attr_accessor :name, :artist, :album
+
+ def initialize
+ @name = { valid: [], invalid: [] }
+ @artist = { valid: [], invalid: [] }
+ @album = { valid: [], invalid: [] }
+ end
+
+ def &(other)
+ name.merge!(other.name) { |key, oldval, newval| oldval | newval }
+ artist.merge!(other.artist) { |key, oldval, newval| oldval | newval }
+ album.merge!(other.album) { |key, oldval, newval| oldval | newval }
+ self
+ end
+
+ def !
+ name[:valid], name[:invalid] = name[:invalid], name[:valid]
+ artist[:valid], artist[:invalid] = artist[:invalid], artist[:valid]
+ album[:valid], album[:invalid] = album[:invalid], album[:valid]
+ self
+ end
+end
+
+class Criteria
+ attr_accessor :list
+
+ def initialize(list = [])
+ @list = list
+ end
+
+ def &(other)
+ criteria = list.product(other.list).map do |criteria|
+ criteria.inject(Criterion.new, :&)
+ end
+ Criteria.new(criteria)
+ end
+
+ def |(other)
+ criteria = list + other.list
+ Criteria.new(criteria)
+ end
+
+ def !
+ criterion = list.inject(Criterion.new, :&)
+ Criteria.new([!criterion])
+ end
+
+ def self.name(text)
+ criterion = Criterion.new
+ criterion.name[:valid] << text
+ Criteria.new([criterion])
+ end
+
+ def self.artist(text)
+ criterion = Criterion.new
+ criterion.artist[:valid] << text
+ Criteria.new([criterion])
+ end
+
+ def self.album(text)
+ criterion = Criterion.new
+ criterion.album[:valid] << text
+ Criteria.new([criterion])
+ end
+end
+
+class Song
+ attr_accessor :name, :artist, :album
+
+ def initialize(name, artist, album)
+ @name, @artist, @album = name, artist, album
+ end
+
+ def match?(criteria)
+ criteria.list.any? do |criteria|
+ name_match?(criteria) && artist_match?(criteria) && album_match?(criteria)
+ end
+ end
+
+ def name_match?(criterion)
+ valid = criterion.name[:valid]
+ invalid = criterion.name[:invalid]
+ return false if invalid.include?(name) || valid.length > 1
+ valid.empty? || valid.include?(name)
+ end
+
+ def artist_match?(criterion)
+ valid = criterion.artist[:valid]
+ invalid = criterion.artist[:invalid]
+ return false if invalid.include?(artist) || valid.length > 1
+ valid.empty? || valid.include?(artist)
+ end
+
+ def album_match?(criterion)
+ valid = criterion.album[:valid]
+ invalid = criterion.album[:invalid]
+ return false if invalid.include?(album) || valid.length > 1
+ valid.empty? || valid.include?(album)
+ end
+end