1. Класика и джаз

Краен срок: 13:00, 28 октомври 2008

Крайният срок вече изтече. Не може да пращате решения.

Сценарият

Представете си, че имаме каталог с музиката, която слушаме. Искаме да му задаваме въпроси от рода на:

  • Дай ми всички песни на този изпълнител.
  • Дай ми всички меланхолични джаз песни.
  • Дай ми всички песни, които имат буквата “е”.
  • Дай ми всички песни, в които има саксофон.

Всяка песен в нашия каталог има следните неща:

  • name – Име (“My Favourite Things”)
  • artist – Изпълнител или композитор (“John Coltrane”)
  • genre – Жанр с опционален поджанр (“Jazz”)
  • subgenre – Поджанр (опционален; “Bebop”)
  • tags – Етикети (списък от символи [:saxophone, :popular, :jazz, :bebop, :cover])

Песните са записани в текстов файл със следния формат:

My Favourite Things;    John Coltrane;      Jazz, Bebop;        popular, cover
Greensleves;            John Coltrane;      Jazz, Bebop;        popular, cover
Alabama;                John Coltrane;      Jazz, Avantgarde;   melancholic
Acknowledgement;        John Coltrane;      Jazz, Avantgarde;
Afro Blue;              John Coltrane;      Jazz;               melancholic
'Round Midnight;        John Coltrane;      Jazz;
My Funny Valentine;     Miles Davis;        Jazz;               popular
Tutu;                   Miles Davis;        Jazz, Fusion;
Miles Runs The Voodo Down; Miles Davis;     Jazz, Fusion;
Boplicity;              Miles Davis;        Jazz, Bebop;
Autumn Leaves;          Bill Evans;         Jazz;               popular
Waltz for Debbie;       Bill Evans;         Jazz;
'Round Midnight;        Thelonious Monk;    Jazz, Bebop;
Ruby, My Dear;          Thelonious Monk;    Jazz;               saxophone
Fur Elise;              L.v. Beethoven;     Classical;          popular
Moonlight Sonata;       L.v. Beethoven;     Classical;          popular
Pathetique;             L.v. Beethoven;     Classical;
Toccata e Fuga;         J.S. Bach;          Classical, Baroque; popular
Goldberg Variations;    J.S. Bach;          Classical, Baroque;
Eine Kleine Nachtmusik; W.A. Mozart;        Classical;          popular, violin

  • Стойностите са разделени с точка и запетая (;)
  • Може да има повторения както в имена на песни, така и на артисти
  • Жанрът и поджанрът са в едно поле, като вторият е опционален. Ако го има, разделени са със запетая.
  • Последното поле е списък от етикети, разделени със запетаи. Може да е празно.

Освен от изрично изброените, една песен може да получава етикети от две други места – артист и жанрове.

Знаем, че всички песни на Колтрейн имат саксофон, а пък Бах пише полифонична музика за пиано. Затова, освен този файл, имаме и такъв хеш:

artist_tags = {
  "John Coltrane" => [:saxophone],
  "J.S. Bach" => [:piano, :polyphony],
}

Горното казва, че всички песни на Колтрейн трябва да имат етикет :saxophone, а всички на Бах – етикети :piano и :polyphony.

Жанрът и поджанрът трябва също да дават етикети. Ако една песен е “Jazz, Bebop”, тя трябва да получи етикетите :jazz и :bebop (изцяло малки букви). Ако е само “Jazz”, получава само един етикет – :jazz .

Идеята

Първо трябва да създадете обекти, които представят песен. Няма значение от какъв клас са, стига да имат следните методи:

# My Favourite Things;    John Coltrane;      Jazz, Bebop;        popular
song.name     # "My Favourite Things"
song.artist   # "John Coltrane"
song.genre    # "Jazz"
song.subgenre # "Bebop"
song.tags     # [:popular, :jazz, :bebop, :saxophone] - редът няма значение

# Eine Kleine Nachtmusik; W.A. Mozart;        Classical;          popular
song.name     # "Eine Kleine Nachtmusik"
song.artist   # "W.A. Mozart"
song.genre    # "Classical"
song.subgenre # nil
song.tags     # [:classical, :popular] - отново, редът няма значение

Трябва да дефинирате клас, представящ музикалната колекция:

collection = Collection.new(file_contents_as_string, artist_tags)

flie_contents_as_string е текстовият файл, прочетен в низ.

Колекциите трябва да дефинират метод find:

class Collection
  def find(result, what = {})
    # ...
  end
end

Няколко примера как трябва да работи find:

# Намира всички песни с етикет jazz:
collection.find :song, :tags => :jazz

# Намира всички песни, които имат двата етикета jazz и piano:
collection.find :song, :tags => [:jazz, :piano]

# Намира всички песни, които имат етикет jazz и нямат етикет piano:
collection.find :song, :tags => [:jazz, :piano!]

# Намира всички популярни песни на Джон Колтрейн:
collection.find :song, :tags => :popular, :artist => "John Coltrane"

# Връща имена на песни, които започват с думичката "My":
collection.find :name, :name => /^My\b/

# Връща имената на всички артисти, които имат поне една песен с етикет jazz:
collection.find :artist, :tags => :jazz

# Извежда име / артист за всички песни със саксофон:
collection.find :song, :tags => :saxophone do |song|
  puts "#{song.name} / #{song.artist}"
end

# Намира всички класически песни с по-дълги имена:
collection.find :song, :filter => lambda { |song| song.name.length > 15 }, :tags => :classical

Спецификацията

Да се създаде клас Collection, със:

  • Конструктор, вземащ два аргумента
    • Първият е текстов низ, съдържащ каталог с песни, във показания по-горе формат.
    • Вторият е хеш, съпоставящ име на артист (низ) с етикети (масив от символи), които всички негови песни трябва да имат.
  • Метод find(result, what = {}). what дефинира кои песни да се търсят, докато result – под каква форма да се върне информацията за тях.
    • what[:tags] – Съдържа символ или списък от символи. Ограничава резултатите до песни, притежаващи всички етикети. Ако някой етикет завършва на удивителна (!), ограничава песните до тези, които нямат този етикет.
    • what[:name] – Низ или регулярен израз. Ако е низ, ограничава до песни, чието име съвпада с низа. Ако е регулярен израз, ограничава до песни, за които има съвпадение с регулярния израз. {:name => "My"} ограничава до песни, които се казват “My”. {:name => /My/ } ограничава до песни, съдържащи подниза “My”.
    • what[:artist] – Аналогично на предното, но за име на изпълнител.
    • what[:filter] – Ламбда или списък от ламбди. Всяка приема един аргумент, който е песен (с изброените горе методи) и връща булева стойност. find трябва да ограничи резултатите до песни, за които всички ламбди са върнали истина.
    • Обърнете внимание, че критериите са конюнктивни. Търсят се песни, които отговарят на всички.
    • Ако result е символът :song, методът връща списък от песни, отговарящи на зададените критерии.
    • Ако result е някой от символите :name, :artist, :genre или :subgenre , find връща списък от низове, които съдържат съответно имената, изпълнителите, жанровете или поджанровете на песните. В този списък не трябва да има повторения.
    • Може би е очевидно, но ако няма резултати, връщате празен списък. Ако има един резултат, връщате списък с един елемент.
    • Ако find се извика с блок, вместо да връща масив, той трябва да yield-не всички стойности на блока си. Редът няма значение. Ако find е извикан със :song , тогава yield-ва песни. Ако е извикан с :name, yield-ва имена на песни и т.н.
    • Редът на върнатите/yield-натите обекти няма значение.
    • Ако find се извика с празен хеш за what, връща всички песни (всички артисти, всички жанрове и т.н.).
  • Няма никакво значение какво точно ще бъдат песните, стига да имат посочените пет метода.

Друго