Втора задача

  1. Това е мястото за въпроси по втора задача. Всъщност, това е едно от местата за въпроси по втора задача. Ето кратко резюме от лекцията.

    Задачата е публикувана. Времето е една седмица. Моето решение е около 100 реда. Това значи, че е добре да започнете да я решавате възможно най-рано.

    Този път задачата ползва skeptic. Изтеглете си skeptic и си проверявайте решението с него на вашата машина. Не отлагайте това за последния момент, понеже сайта не приема решения, които не отговарят на критериите. Ако оставите това за накрая, може да се окаже, че решението ви ще има нужда от сериозно преработване.

    Ако имате нужда от помощ, първото нещо което може да направите е да зададете въпрос тук. Така е най-хубаво, понеже всички могат да видят отговора.

    Ако това не ви върши работа (понеже въпроса ви включва това да видим решението, а както всички знаем, публикуване на част от решението е форма на преписване), имате няколко алтернативи. Едната е да предадете нещо и после да оставите коментар. Вече има бутон "Коментари на моето решение" когато отворите задачата (стига да сте предали решение). Натискате го и оставяте коментар.

    Другата алтернатива е да ни пишете мейл. Моля, не ни пишете във Facebook. Моля, не ни пишете и на личните мейли. Пишете на мейла на курса (fmi@ruby.bg), понеже така и двамата можем да ви отговорим. Ако трябва да споделяте код с нас, не го paste-вайте в мейла, а направете private gist.

    И за пореден път, не забравяйте примерния тест.

  2. Няколко произволни коментара към решенията до момента:

    Някои от вас наследяват от Proc. Това не е правилно. Може да минете без наследяване, а с композиция. Вече говорехме, че композицията е за предпочитане пред наследяването. Отделно, някои от решенията нарушават LSP, за който ще говорим следващия път.

    Няколко от вас подават ламбда литерал като аргумент на функция. Това не е много хитро, понеже може да подадете блок. Не е фатална грешка, но определено не е хубав код.

  3. Операторите в Ruby имат приоритет, който в този случай се наследява. Можеш лесно да го пробваш, ако измислиш някакъв прост експеримент. Например, (1 & 0) | 2 и 1 & (0 | 2) дават различен резултат. Пробвай 1 & 0 | 2 и виж кой от двата ще даде. Това е приоритета, който ще имаш.

    Това е приоритета, който се очаква да имат критериите.

    Ако си в по-експериментално настроиение, можеш да изпълниш ruby --dump=parsetree -e 'a & b | c'

  4. Нещо много важно, което май не сме го казвали публично:

    Употребата на eval или сродни неща в решението на втора задача е забранено. Ако ви видим да ползвате eval или други методи от този клас, ще ви взимаме точки.

    Има как да заобиколите това ограничение.

  5. ...просто защото eval е глупав начин да направите нещо, когато има далеч по-прост и разбираем такъв.

    Това настрана, ето още две неща.

    Не се заобикаляйте ограниченията - впишете се в тях. Те са там с някаква причина. Ако не може да си съберете кода в тях, значи има нещо, което не разбирате. Научете го. Един пример за заобикаляне е ако броя методи не ви стига да си дефинирате още в модул, който да миксирате. Друг пример е да разделите един 12-редов метод на 3 четири-редови, макар никой от тях да няма логика сам по себе си. Това е лош код, не го правете.

    Ограничението за trailing whitespace? Ако не можете да си конфигурирате текстовия редактор и вместо това просто не оставяте празни редове между методите и класовете, това пак е заобикаляне. Или си оправете редактора или бъдете готови за по-малко точки. Ако имате нужда от помощ - питайте.

    Monkey patch-а е лоша идея. Обяснихме защо. Ако не сте разбрали, това е ОК - ще обясняваме пак. Но това не е валидна причина да го правите.

  6. Булевата стойност на всичко, освен nil и false e true. Оператора ! обръща булевата стойност на израза. Примери:

    a = nil
    !a # true
    
    a = false
    !a # true
    
    a = true
    !a # false
    
    a = 42
    !a # false
    
    a = "string"
    !a # false
    
    a = 0
    !a # false
    
    a = Array.new
    !a # false
    
    a = lambda { |x| x ** 2 }
    !a # false
    

    Операторът може да се override-не:

    def !
    ...
    end
    
  7. class Test
      attr_reader :f
    
      def initialize(f)
        @f = f
      end
    
      def &(test)
        p @f
        p test.f
        self
      end
    
      def |(test)
        p @f
        p test.f
        self
      end
    end
    
    p Test.new(1) | Test.new(2) | Test.new(3) & Test.new(4)
    

    Има разлика в приоритета на операциите, което е доста странно и затрудняващо. Горният пример извежда 1, 2, 3, 4, 1, 3 (и накрая е 1), което значи, че първо оценява |.

    От друга страна ако се махне Test.new(2), се извежда 3, 4, 1, 3 (и накрая е 1), което изпълнява първо &.

    Въпроса ми е ще има ли повече от два критерия навързани с побитовите оператори или тестовете ще са само като от примерите (с по два)?

  8. Ще има повече от два критерия. Могат да се композират неограничен брой критерии. Не е нужно критериите дори да бъдат смислени. Например: (Criteria.artist('String') & !Criteria.artist('Sting')) & Criteria.artist('Eva Cassidy').

    Иначе, не знам защо това те объкрва. Напълно нормално следствие от приоритетите и асоциативността на операторите е. И двата оператора са ляво-асоциативни, като & има по-висок приоритет от |. Съответно в a | b | c & d може да се оцени a | b независимо от c & d и после да се направи | на резултата, което е точно това, което ти показват числата.

    В a | b & c, обаче, първо трябва да се оцени b & c (защото е с по-висок приоритет) и след това трябва да се направи | с a, което отново е резултата.

    Кода, с който експериментираш не е много оптимален за да разбереш какво се случва. Вместо да правиш така, постави принтирането в конструктора и промени операторите на def &(other) Test.new("(#{f} & #{other.f}") end. Така ще добиеш по-добра представа какво се случва.

  9. Ето няколко неща, които за които стана въпрос на лекцията.

    Някои от вас не остават празни редове между методите. Това е супер грозно и нарушава конвенциите. Правите го, защото имате проблем с trailing whitespace-а на празните редове. Искам да напомня, че ако не можете да се впишете в ограничение, има нещо, което не разбирате и трябва да научите. В този случай е как да накарате редактора да премахва тези trailing whitespace. Почти всички редактори имат или настройка, или команда, която го прави. Просто се разтърсете. Ако ви мързи и предадете бе празни редове между дефинициите на методите, това ще ви струва 3 точки. Мислете за това като "такса мързел".

    Защо класовите методи, а не конструктори?. Причината да искаме да правите Collection.parse(INPUT) вместо Collection.new(INPUT) е абстракция. Второто предполага какви са аргументите на конструктора. Първото - не. Съответно, първото ви дава по-голяма свобода как да моделирате колекцията. Аналогично за критериите - Criteria.artist("String") не казва от какъв тип е резултата и остава на вас да решите. Искаме добре да помислите по въпроса и да вземете някакво решение. Когато крайния срок мине ще ви покажем няколко варианта.

    Доброто ООП значи "много малки класове". Ако трябваше да решите тази задача на Java, добрия код щеше да съдържа поне 9 класа. Всеки щеше да е доста малък. В Ruby може да минете метър с по-малко, но и 9 е добре.

Трябва да сте влезли в системата, за да може да отговаряте на теми.