OOP prakticky: Derivace

…aneb derivovat umí i cvičená opice!
24. 12. 2008

Dostáváme se k zajímavější látce, a to jsou derivace. Pokud derivovat neumíte, nevadí, je to vesměs jednoduchá záležitost, aplikace několika triviálních vzorečků. Ty můžete najít třeba na Wikipedii.

Teď musíme přijít na to, kam metodu definovat. Potřebujeme, aby byl derivovatelný každý výraz, který vytvoříme. Metodu derivace dostaneme do každé třídy tak, že ji přidáme do Otce všech tříd, do Matematického výrazu. Tato metodu bude v této třídy opět pouze abstraktní, my nemůžeme vědět, jak budou derivovány jednotlivé další výrazy. Třídu upravíme takto:

   1: abstract class MatematickyVyraz
   2: {
   3:     public abstract MatematickyVyraz Kopie();
   4:     public abstract MatematickyVyraz Derivace();
   5: }

Nyní máme v každé třídě přístup k metodě Derivace(), avšak nyní ji musíme v každé třídě definovat. Tak do toho. Začneme u konstanty. Jaká je derivace konstanty? Inu, nula. Pokud zderivujete jakékoliv číslo, získáte nulu. Takže v konstantě bude derivace definována takto:

   1: public override MatematickyVyraz Derivace()
   2: {
   3:     return new Konstanta();
   4: }

Teď šup na proměnnou. Jaká je derivace proměnné? Jednička. Takže to definujme:

   1: public override MatematickyVyraz Derivace()
   2: {
   3:     return new Konstanta(1);
   4: }

Teď se podívejte na Binární Výrazy. Mají Binární Výrazy jednotnou derivaci, abychom ji mohli nadefinovat přímo ve třídě Binárních Výrazů? Nemají, součet, rozdíl, součin i podíl se derivují jinak. Začneme derivací součtu. Derivace součtu je součet derivací, tedy (a + b)' = a' + b' (apostrof je derivace). Metoda ve třídě Součet by vypadala takto:

   1: public override MatematickyVyraz Derivace()
   2: {
   3:     return new Soucet(A.Derivace(), B.Derivace());
   4: }

Vytvoříme novou instanci třídy Součet a jako argumenty mu předáme zderivované A a zderivované B. Děláme přesně to, co po nás chce vzorec. Derivaci u odečítání vypadá úplně stejně, jen budeme vytvářet instanci Rozdílu:

   1: public override MatematickyVyraz Derivace()
   2: {
   3:     return new Rozdil(A.Derivace(), B.Derivace());
   4: }

Abychom si ukázali i trochu složitější derivace, nadefinujeme si konečně třídy Součin a Podíl. Budou to opět potomci Binárního Výrazu, ale jinak na těch třídách nebude nic zajímavého. Následuje Součin:

   1: class Soucin : BinarniVyraz
   2: {
   3:     public Soucin() : base() { }
   4:     public Soucin(MatematickyVyraz A, MatematickyVyraz B) : base(A, B) { }
   5:  
   6:     public override string ToString()
   7:     {
   8:         return ToBinaryString("*");
   9:     }
  10:  
  11:     public override MatematickyVyraz Kopie()
  12:     {
  13:         return new Soucin(A.Kopie(), B.Kopie());
  14:     }
  15: }

A Podíl. Zde si všimněte, že v rámci zapouzdření kontrolujeme, jestli se náhodou nepokoušíme dělit nulou.

   1: class Podil : BinarniVyraz
   2: {
   3:     public Podil() : base() { }
   4:     public Podil(MatematickyVyraz A, MatematickyVyraz B)
   5:         : base(A, B)
   6:     {
   7:  
   8:     }
   9:  
  10:     private MatematickyVyraz _B;
  11:     public override MatematickyVyraz B
  12:     {
  13:         get { return _B; }
  14:         set
  15:         {
  16:             if(value is Konstanta)
  17:                 if((value as Konstanta).Hodnota == 0)
  18:                     throw new Exception("Nemůžeme dělit nulou!");
  19:  
  20:             _B = value;
  21:         }
  22:     }
  23:  
  24:     public override string ToString()
  25:     {
  26:         return ToBinaryString("/");
  27:     }
  28:  
  29:     public override MatematickyVyraz Kopie()
  30:     {
  31:         return new Podil(A.Kopie(), B.Kopie());
  32:     }
  33: }

Všimněte si ještě, že jsme přepsali set blok u „B“. Abychom toto vůbec mohli udělat, musíme přepsat rodičovskou třídu Binární Výraz a označit tam vlastnost B za přepisovatelnou pomocí klíčového slova C# virtual.

   1: abstract class BinarniVyraz : MatematickyVyraz
   2: {
   3:     public MatematickyVyraz A;
   4:     public virtual MatematickyVyraz B;
   5:  
   6:     public string ToBinaryString(string Znamenko)
   7:     {
   8:         return String.Format("({0} {1} {2})", A.ToString(), Znamenko, B.ToString());
   9:     }
  10: }

A teď si pojďme definovat derivace u Součinu a Podílu. Začneme Součinem, kde platí vzorec: (a * b)' = (a' * b) + (a * b'). Vytvoření derivace tedy bude u Součinu o fous složitější než u předchozích tříd:

   1: public override MatematickyVyraz Derivace()
   2: {
   3:     Soucet soucet = new Soucet();
   4:  
   5:     soucet.A = new Soucin(A.Derivace(), B.Kopie());
   6:     soucet.B = new Soucin(A.Kopie(), B.Derivace());
   7:  
   8:     return soucet;
   9: }

U podílu platí vzorec: (a / b)' = ((a' * b) + (a * b')) / b2. Implementace ve třídě Podíl:

   1: public override MatematickyVyraz Derivace()
   2: {
   3:     Soucet soucet = new Soucet();
   4:  
   5:     soucet.A = new Soucin(A.Derivace(), B.Kopie());
   6:     soucet.B = new Soucin(A.Kopie(), B.Derivace());
   7:  
   8:     Podil podil = new Podil();
   9:  
  10:     podil.A = soucet;
  11:     podil.B = new Soucin(B.Kopie(), B.Kopie());
  12:  
  13:     return podil;
  14: }

Zkusme si, jak nám to derivuje.

   1: static void Main(string[] args)
   2: {
   3:     Konstanta a = new Konstanta(10);
   4:     Konstanta b = new Konstanta(20);
   5:  
   6:     Promenna x = new Promenna("x");
   7:     Promenna y = new Promenna("y");
   8:  
   9:     Soucet soucet = new Soucet(a, x);
  10:     Soucin soucin = new Soucin(b, y);
  11:  
  12:     Rozdil rozdil = new Rozdil(soucet, soucin);
  13:  
  14:     Console.WriteLine("Před derivací: {0}", rozdil.ToString());
  15:     Console.WriteLine("Po derivaci: {0}", rozdil.Derivace().ToString());
  16:  
  17:     Console.Read();
  18:  
  19:     /*
  20:     Výstup z programu:
  21:     
  22:     Před derivací: ((10 + x) - (20 * y))
  23:     Po derivaci: ((0 + 1) - ((0 * y) + (20 * 1)))
  24:     */
  25: }

Funguje, derivuje nám to správně. Co nám teď zbývá? Substituce proměnné a zjednodušování výrazů. Koukneme se nejprve na substituci proměnné, to bude jednodušší.