Втора задача

Предадени решения

Краен срок:
06.11.2013 17:30
Точки:
6

Срокът за предаване на решения е отминал

To-do Списъци

Имплементирайте класове за работа с To-do списъци. Те трябва да могат да прочитат текстов низ с определен формат и да предлагат определена функционалност.

Форматът

Текстовият низ, дефиниращ To-do списъка, има следния формат:

TODO    | Eat spaghetti.               | High   | food, happiness
TODO    | Get 8 hours of sleep.        | Low    | health
CURRENT | Party animal.                | Normal | socialization
CURRENT | Grok Ruby.                   | High   | development, ruby
DONE    | Have some tea.               | Normal |
TODO    | Destroy Facebook and Google. | High   | save humanity, conspiracy
DONE    | Do the 5th Ruby challenge.   | High   | ruby course, FMI, development, ruby
TODO    | Find missing socks.          | Low    |
TODO    | Occupy Sofia University      | High   | #ДАНСwithMe, #occupysu, #оставка

Всеки ред представлява една задача от To-do списъка. Всяка задача се характеризира със статус, описание, приоритет и етикети, разделени от символа |. Етикетите могат да са повече от един и в такъв случай, те се разделят със запетая.

Приемете, че входът винаги е правилно форматиран и (очевидно) символът | няма да бъде в статус, описание, приоритет или таг.

To-do списъкът

Създаването на To-do списък трябва да става така:

todo_list = TodoList.parse(text)

Където text е текстов низ със съдържание във формата, описан по-горе (с цел опростена проверка).

Отделно, To-do списъците трябва да имплементират Enumerable. Редът на задачите трябва да бъде същият като подадения в text.

Всяка задача от списък трябва да е обект с (поне) четири метода - status, description, priority и tags, които връщат съответната информация.

  • status - статусът на задачата като символ (:todo, :current или :done).
  • description - низ с описанието на задачата.
  • priority - приоритетът на задачата като символ (:low, :normal или :high).
  • tags - списък от етикети като низове. Например, етикетите ruby course, FMI, development трябва да бъдат върнати в следния вид ['ruby course', 'FMI', 'development'].

Функционалността

Филтриране

To-do списъкът трябва да има метод filter, който да приема критерии за филтриране. Възможни критерии са:

Criteria.status(:todo)              # Критерий за статус.
Criteria.priority(:normal)          # Критерий за приоритет.
Criteria.tags(%w[food development]) # Критерий за етикети.

Criteria е някакъв обект/модул/клас, който има съответните три метода. Всеки от тях връща някакъв обект, представляващ критерий, по който да може да се филтрира To-do списък, подавайки го като аргумент на TodoList#filter. Например, ако искате да намерите всички задачи, които са с висок приоритет, може да го направите така:

todo_list.filter Criteria.priority(:high)

filter връща подсписък на филтрирания To-do списък. Няма значение от какъв клас е, стига да има същите методи като To-do списъка.

Отделно, критериите могат да се комбинират с &, |, !, където първото е конюнкция, а второто - дизюнкция. Така например може да намерите:

  • Всички задачи с етикет development заедно с всички задачи с нисък приоритет.

      todo_list.filter Criteria.tags(['development']) | Criteria.priority(:low)
    
  • Всички задачи с висок приоритет, но без етикет food

      todo_list.filter Criteria.priority(:high) & !Criteria.tags(['food'])
    

Забележете, че критерият за етикети приема списък от етикети и филтрирането по тях връща тези задачи, които съдържат всички етикети, посочени в критерия. Например:

todo_list.filter Criteria.tags(%w[development ruby])

Ще върне две задачи: Grok Ruby. и Do the 5th Ruby challenge.. Докато:

todo_list.filter Criteria.tags(%w[development FMI])

Ще върне само Do the 5th Ruby challenge..

Обединяване на подсписъци на To-do списъци

Подсписъците, връщани от filter, трябва да имат допълнително и метод adjoin, който да може да обедини два подсписъка. Например, ако искате да вземете всички задачи с етикет development и всички задачи с нисък приоритет, може да го направите по два начина:

todo_list.filter Criteria.tags(['development']) | Criteria.priority(:low)
todo_list.filter(Criteria.tags(['development'])).adjoin(todo_list.filter(Criteria.priority(:low)))

При такова обединение на подсписъци, редът на задачите в резултата не е дефиниран.

Брой на задачи според статуса

  • TodoList#tasks_todo - връща броя на задачите, които трябва да се направят (със статус :todo).

  • TodoList#tasks_in_progress - връща броя на задачите, които са в прогрес (със статус :current).

  • TodoList#tasks_completed - връща броя на завършените задачи (със статус :done).

Завършеност на To-do списъка

To-do списъкът трябва да има и метод completed?, който връща дали To-do списъкът е завършен.

To-do списък е завършен, ако всички задачи в него са със статус :done.

Бележки

  • Изисквания към константи и интерфейс:

    • TodoList с "класов" метод parse и методи filter и adjoin, които връщат нещо, което има същите методи.

    • Criteria с "класови" методи status, priority и tags, които връщат обекти, имплементиращи оператори &, | и !. Въпросните обекти могат да бъдат подавани на filter.

    • Подсписъкът, върнат от TodoList.parse, да е Enumerable и да може да се третира като стандаретн обект от този тип. Това важи и за обектите тип "подсписък на To-do списък".

    • Елементите (обектите задачи), които To-do списъкът подава при обхождане, да имат методи status, description, priority и tags.

  • Обърнете внимание, че:

    • Не ни интересува типът на върнатото от TodoList.parse.

    • Не ни интересува дали To-do списък и "подсписък на To-do списък" са един и същи или два различни класа, стига и двата да отговарят на един и същ интерфейс (да могат да бъдат обхождани).

    • Аналогично, не ни интересува типът на обекта, връщан от Criteria, стига да отговаря на съответните условия.

    • Не ни интересува типът на задачите в To-do списъка, а само интерфейсът към тях.

    • Свободни сте да вземете всички тези решения както намирате за уместно.

  • Ако ползвате eval или нещо сродно, ще ви взимаме точки.

  • Редът на задачите винаги е като този в низа. Изключение прави само резултатът от adjoin.

  • Възникват интересни въпроси за това кога две задачи са равни. За простота, приемете че adjoin се прави само между посписъци на един и същ To-do списък. Възможно е да се прави и между To-do списък и негов подсписък. Няма да се прави между подсписъци от два различни To-do списъка, обаче. Т.е., не е дефинирано какво прави TodoList.parse(text).adjoin(TodoList.parse(text)).

  • filter и adjoin връщат нов обект, а не променят стария.

Примерен тест

Примерния тест за задачата може да намерите в хранилището с домашните ето тук. Как да си го пуснете може да видите в ръководството.

Ограничения

Тази задача има следните ограничения:

  • Най-много 80 символа на ред
  • Най-много 4 реда на метод
  • Най-много 1 нива на влагане

Ако искате да проверите дали задачата ви спазва ограниченията, следвайте инструкциите в описанието на хранилището за домашните.

Няма да приемаме решения, които не спазват ограниченията. Изпълнявайте rubocop редовно, докато пишете кода. Ако смятате, че rubocop греши по някакъв начин, пишете ни на fmi@ruby.bg, заедно с прикачен код или линк към такъв като private gist. Ако пуснете кода си публично (например във форумите), ще смятаме това за преписване.