Йоан обнови решението на 31.10.2011 16:27 (преди около 13 години)
+class Collection
+ def initialize(songs_as_string, artist_tags = {})
+ @pieces = songs_as_string.lines.map { |string| Piece.from_string string }
+ @pieces.each { |piece| piece.tags.push *artist_tags[piece.artist] }
+ end
+
+ def find(criteria = {})
+ @pieces
+ .select(&name_filter(criteria[:name]))
+ .select(&artist_filter(criteria[:artist]))
+ .select(&tag_filter(criteria[:tags]))
+ .select(&criteria[:filter])
+ end
+
+ private
+
+ NullFilter = lambda { |piece| true }
+
+ def name_filter(name)
+ return lambda { |piece| piece.name == name } if name
+ NullFilter
+ end
+
+ def artist_filter(artist)
+ return lambda { |piece| piece.artist == artist } if artist
+ NullFilter
+ end
+
+ def tag_filter(tags)
+ case tags
+ when [], nil
+ NullFilter
+ when Array
+ lambda do |piece|
+ first, *rest = *tags
+ tag_filter(first)[piece] and tag_filter(rest)[piece]
+ end
+ when /.*!$/ # ends in an exclaimation
+ lambda { |piece| not piece.tags.include?(tags[0...-1]) }
+ else
+ lambda { |piece| piece.tags.include? tags }
+ end
+ end
+end
+
+class Piece
+
+ FieldSeparator = '.'
+ SubfieldSeparator = ','
+
+ attr_reader :name, :artist, :genre, :subgenre, :tags
+
+ def initialize(name, artist, genres, tags = '')
+ @name = name.strip
+ @artist = artist.strip
+ @genre, @subgenre = Piece.split_field genres
+ add_tags tags
+ add_tags @genre.downcase
+ add_tags @subgenre.downcase if @subgenre
+ end
+
+ def add_tags(tags_as_string)
+ tags = Piece.split_field tags_as_string
+
+ @tags ||= []
+ @tags.push(*tags).uniq!
+ end
+
+ def self.from_string(string)
+ Piece.new *string.split(FieldSeparator)
+ end
+
+ private
+ def self.split_field(string_array)
+ string_array.split(SubfieldSeparator).map { |x| x.strip }
+ end
+
+end
Ако си изкормя целия код за tag-ове, май ще започне да изглежда по-приемливо.
Конвенцията за константи е NULL_FILTER
, FIELD_SEPARATOR
и SUBFIELD_SEPARATOR
. Константи от вида FieldSeparator
се ползват само за имена на класове/модули.
Хвърли едно око на този Ruby Style Guide.
Също, хвърли едно око на Object#freeze — често се ползва за стойности, присвоени на константи.
Бацов е спасява деня!
А в замразяването има смисъл, наистина... въпреки, че ще ми е странно, ако някой третира низове като mutable, въпреки че не ги е инстанцирал самият той.