Posts Tagged ‘ Szkola

Prosty błąd – proste rozwiązanie

Zmagam się od jakiegoś czasu z ASP .NET Web Service-ami. Utworzyłem nowy Web Service i postanowiłem zmienić nazwę pliku Web Servicu ze standardowego Service1 na ServiceAdmin. Zmiana przebiegła bezproblemowo po uruchomieniu otrzymałem taki oto błąd:

webserviceerror

Zgodnie ze wskazówkami błędu swoją uwagę skierowałem na plik .asmx – standardowo kliknąłem dwukrotnie w Visual Studio oczekując na pojawienie się magicznej xml-owej linijki. Niestety Visual Studio przekierował mnie do kodu Web Servicu. Po dobrych 10 minutach moje mało spostrzegawcze oczy po kliknięciu prawym przyciskiem myszy na plik ujrzały opcję: Open With… -> XML Editor:

<%@ WebService Language="C#" CodeBehind="ServiceAdmin.asmx.cs" Class="WBAdmin.Service1" %>

Visual Studio nie zrefaktorował atrybutu „Class” – wprowadziłem ręcznie to czego potrzebował kompilator podczas uruchamiania.

<%@ WebService Language="C#" CodeBehind="ServiceAdmin.asmx.cs" Class="WBAdmin.ServiceAdmin" %>

W przeglądarce ujrzałem magiczne „Hellow World”. ;)

To co dała mi praktyka i to czego nie dała mi szkoła

Majowym wpisem pod tytułem „Pierwsza praca” rozpocząłem poszukiwania miejsca gdzie mógłbym odbyć praktykę zawodową. Wiele firm zajmujących się produkcją oprogramowania realizuje programy letnich praktyk studenckich skierowane do studentów ostatnich lat studiów. Kierując się bardzo pomocnymi komentarzami pod wspomnianym wyżej wpisem rozpocząłem wysyłanie CV. Zakładałem, że na drodze do upragnionych pierwszych poczynań programistycznych stanie mi rozmowa kwalifikacyjna (to raczej naturalne), problem znalezienia lokum w mieście do którego się udam itd. – Jednak nie przypuszczałem, że dodatkowo uczelnia na której studiuje (PWSZ w Białej Podlaskiej) dołączy do tej listy.

Szanse sprawdzenia swoich umiejętności dała mi lubelska firma „UHC CompuGROUP holding” – zajmująca się tworzeniem oprogramowania dla sektora medycznego. Określiłbym, że rekrutacja odbyła się w dwóch etapach – przesłanie CV zakończyło się prośbą o dostarczenie „próbek kodu” – aplikacji które napisałem do tej pory. Następnie po kilkudniowej ciszy otrzymałem telefon i odbyła się 2,5 godzinna rozmowa wstępna :). Kilka dni później dowiedziałem się – kto będzie moim opiekunem praktyk oraz do jakiego działu zostałem przydzielony (Dział Systemów Ekonomicznych). Pozostało mi dopiąć kwestie formalne o czym napiszę szerzej pod koniec.

Moim głównym celem było zmierzenie się z realnymi problemami na stanowisku programisty. Otrzymałem pierwsze zadanie – opiekun rzeczowo wytłumaczył mi co mam zrobić, jakiego wyniku oczekuje. Moje potknięcia i niedociągnięcia były traktowane z pełną wyrozumiałością, co niesamowicie mnie motywowało do cięższej pracy. Miałem czas na naukę, nie obawiałem się pytać nawet o najgłupsze rzeczy, atmosfera panująca w UHC przerosła moje najśmielsze oczekiwania. Mimo, że nie do końca zajmowałem się tym czym bym chciał – wcale mi to nie przeszkadzało! To co robiłem sprawiało mi ogromną przyjemność. :)

Praktyki uzmysłowiły mi, że jeżeli trafie w przyszłości do firmy takiej jak UHC to szybko się rozwinę. Szukajcie właśnie takiej pozytywnej atmosfery gdy stawiacie pierwsze kroki ku wymarzonej pracy czy też praktyki. Polecam przeczytać wpis Procenta – dzięki któremu zwracałem uwagę na wszystko co się wokół dzieje już podczas rozmowy kwalifikacyjnej.

Pozdrawiam i jeszcze raz dziękuję Markowi, Tomkowi, Andrzejowi, Gabrysiowi i Kamilowi za wspaniałą atmosferę i to wszystko czego się od Was nauczyłem. :)

Jak już wspomniałem największą przeszkodą do odbycia upragnionych praktyk była uczelnia na której studiuje. W PWSZ w Białej Podlaskiej studenci II roku na kierunku informatyka mają obowiązek odbycia 4 tygodniowej praktyki studenckiej – wypełniłem ten obowiązek jednak miejsce praktyk odbiegało dalece od moich oczekiwań. Byłem świadomy, że większość firm tworzących oprogramowanie przyjmuję tak naprawdę studentów III roku, aby mogli się zmierzyć z realiami programistycznymi. Postanowiłem fakt ukończenia III roku uwieńczyć zdobyciem miejsca gdzie postawie pierwsze kroki w prawdziwej pracy.

Poproszono mnie w firmie UHC o umowę na praktyki z moją uczelnią – pomyślałem, że nie będzie z tym problemu mimo, że będą to moje fakultatywne (ponadprogramowe) praktyki. Zadzwoniłem, więc do Działu Spraw Studenckich skąd skierowano mnie do Działu Praktyk.

Odpowiedz Pani kierownik działu: Nie wydajemy umowy na praktyki ponadprogramowe.

Nie straciłem jeszcze nadziei i zgodnie z podpowiedzią z firmy zadzwoniłem do Biura Karier które na niektórych uczelniach podpisuję takie umowy
i nie ma z tym najmniejszego problemu.

Odpowiedz biura karier: Jest Pan pierwszą osobą pytająca o takie rzeczy. Proszę się skierować do Działu Praktyk. :/

Ostatnia deska ratunku – napisałem do prorektora uczelni. Pismo wróciło do działu praktyk i zgadnijcie co ? :) Żal.pl – Odpowiedź identyczna jak wyżej – Nie wydajemy bo…. Ciekaw jestem jaką opinie wyraził Pan prorektor pod moim podaniem – Jeśli w ogóle jakąś wyraził… Nakreśliłem odpowiednim osobom w firmie całą sytuację – przesłano mi firmowy druk umowy między uczelnią, a UHC z którym osobiście (jak dotąd załatwiałem wszystko telefonicznie) udałem się na uczelnie. Odwiedziłem Biuro Karier, skąd powędrowałem do Instytutu Zarządzania (zabawnie ;)) tam Pani poinformowała mnie, że za dwa dni przekaże moją umowę odpowiedniej osobie.

Uwaga teraz najlepsza część tej parodii – W tym samym czasie w firmie ktoś stanął „na głowie” i znalazł sposób, aby podpisać ze mną umowę z wyłączeniem uczelni. Wróciłem do domu przeczytałem maila z tą informacja i sprawa była załatwiona. W mojej głowie pojawiła się myśl – skoro mam już wszystko załatwione to sprawdzę jednak czy uczelnia pomoże w rozwoju swojemu studentowi i podpisze przedłożony druk. Mijają dwa dni – docieram na uczelnie o wyznaczonej godzinie, zgłaszam się do przemiłej Pani i… – „Ojej zapomniałam, ale zaraz zaniosę to Pani doktor..” :) – Uśmiechnąłem się tylko – Bez komentarza… Pani doktor zerknęła i poleciła mi iść z tym do dyrektora instytutu informatyki – paranoja. :) Wziąłem umowę – zrobiłem
z niej ładną kulkę i wyrzuciłem do kosza.

Oto w jaki sposób PWSZ w Białej Podlaskiej pomogło mi odbyć praktyki studenckie, oto jak władze uczelni wsparły moją własną inicjatywę, oto
w jaki sposób wsparły moją chęć rozwoju.

Brawo PWSZ!

Tylko dzięki postawie odpowiednich osób w firmie UHC nie kosztowało mnie to, więcej nerwów za co Bardzo Dziękuję.

Pierwszy krok w stronę pracy inżynierskiej

Postanowiłem przerwać delikatna ciszę w moich poczynaniach blogowych spowodowana sesją egzaminacyjną oraz trwającymi jeszcze upragnionymi praktykami zawodowymi o których szerzej wspomnę w najbliższym wpisie. :) Przede mną ostatni semestr studiów co powoduje, że zaczynam rozmyślać nad częścią praktyczną mojej pracy inżynierskiej. Zamierzam napisać niewielki system do tworzenia i przeprowadzania testów sprawdzających więdzę. Jakiś czas temu zainteresowałem się architekturą SOA (Service Oriented Architecture) – spróbuję niebawem głębiej przyjżeć się jej aspektom i wpleść pewne jej założenia do swojego projektu. Na dzień dzisiejszy w zgłębianiu wiedzy wspomagać mnie będzie ta oto książka:

Service Oriented Architecture For Dummies

Dodatkowo w zakamarkach mojej głowy pojawiają się takie oto technologie:

  • ADO. Entity Framework, Nhibernate
  • Web Services
  • MS SQL Server 2008
  • UML

To wszystko na początek – bez specjalnego rozwijania co i jak zostanie stworzone. Być może jakieś problemy które napotkam i rozwiążę znajdą się tutaj szczegółowo opisane. Trzymajcie kciuki drodzy czytelnicy za moje skromne inżynierskie przedsięwzięcie. ;)

Zabawa w szyfrowanie – RSA

W ramach zaliczenia laboratorium jednego z przedmiotów szkolnych miałem za zadanie napisać niewielki programik szyfrujący. Wybrałem szyfr RSA – jak podaje wikipedia jego nazwa jest akronimem utworzonym z pierwszych liter nazwisk jego twórców – Rivest, Shamir, Adleman. Szyfrowanie i odszyfrowanie tekstu z wykorzystaniem RSA nie jest procesem zbyt skomplikowanym – wymaga jednak operowania dość dużymi liczbami pierwszymi co zmusiło mnie do rezygnacji ze standardowego typu int w C# który miał za mały zakres. W bezdennych zasobach internetowych znalazłem strukturę którą wykorzystałem w swojej implementacji -> BigInt.

Etapy szyfrowania umożliwiające zakodowanie i odkodowanie tekstu:

  1. Wylosuj dwie duże liczby pierwsze p i q
  2. Oblicz n = p * q
  3. Oblicz phi = (p – 1) * (q – 1)
  4. Oblicz e -> Para (e, n) tworzy klucz publiczny wykorzystywany do szyfrowania
  5. Oblicz d -> Para (d, n) tworzy klucz prywatny wykorzystywany do odszyfrowania
  6. Szyfrowanie znaku:
    • Zamiana znaku na kod Ascii
    • Oznaczmy c jako zaszyfrowany znak c = asciiNume mod n
  7. Odszyfrowanie znaku:
    • Aby otrzymać na powrót zaszyfrowany znak: t = cd mod n

Zamiana znaku na kod Ascii i na odwrót:

          char ch = "A";
          int asciiNum = (int)ch;
          ch = (char)asciiNum;

Nie chcę jednak zagłębiać się maksymalnie w poszczególne etapy szyfrowania i odszyfrowania, a skupię się na samym specyficznym sposobie zapisu zaszyfrowanych znaków który sobie wymyśliłem i zakodziłem. ;)

Przykład 1:

Powiedzmy, że mam wyraz „Ola”, który zaszyfrowałem i chcę zapisać do pliku następnie odczytać i odszyfrować. Liczby powstałe w procesie szyfracji: 186220802, 16730814, 137205435. I tutaj pojawia się pytanie w jaki sposób mogę zapisać liczby tak aby osoba otwierająca plik nie domyśliła się, że poszczególne liczby reprezentują znaki? Jednym ze sposobów jest na przykład oddzielanie liczba przecinkami, kropkami lub innymi znakami interpunkcyjnymi. Mnie to jednak nie zadowalało wpadłem więc na pomysł, aby ustalić na stałe znaki „specjalne”, które zostaną zakodowane i „dodane” do powstałych liczb.

Przykład 2:

Ustalam, że moim znakiem specjalnym będzie przecinek następnie szyfruje jego kod i otrzymuję na przykład liczbę: 164916224.
Dodaje do każdej z wyżej powstałych liczb znak specjalny i do pliku wędruję taki oto ciąg liczb 18622080216491622416730814164916224137205435 :)

Jest jednak problem w sytuacji gdy ciąg znaków wygląda tak „O,l,a,,,”, a znakiem specjalnym jest przecinek. Odczytanie z pliku zaszyfrowanego ciągu z użyciem jako znak specjalny przecinka znacząco utrudnia odszyfrowanie – mamy długi ciąg liczb chcemy go podzielić po przecinku, ale tak naprawdę nie wiemy ile razy przecinek mógł wystąpić w danym wyrażeniu. By mieć możliwość swobodnego podziału tego typu ciągu liczb i odzyskać poszczególne liczby, aby je odkodować następnie odczytać oryginalny tekst źródłowy ustaliłem znaki specjalne spoza kodów Ascii. – Są to losowa przeze mnie wybrane trzy dziwne „krzaczki” które mi pięknie oddzielają poszczególne zakodowane znaki.

Klasę realizującą szyfrowanie i odszyfrowanie na podstawie obliczonych kluczy nazwałem Rsa. Jako, że spróbuję zmierzyć się z implementacją innych sposobów szyfrowania utworzyłem interfejs który w moim programie będzie reprezentował klasy szyfrujące i deszyfrujące.

Interfejs ICipher:

public interface ICipher
{
    StringCollection Encrypt(StringCollection sc);
    StringCollection Encrypt(string path);
    StringCollection Decrypt(StringCollection sc);
    StringCollection Decrypt(string path);
    void SaveToFile(string savePath);
}

Klasa Rsa:

/// <summary>
/// Klasa szyfrujaca RSA
/// </summary>
public class Rsa : ICipher
{
   #region Fields & Properties
   private BigInt _n;
   private BigInt _e;
   private BigInt _d;

   /// <summary>
   /// Tablica znakow specjalnych
   /// </summary>
   private readonly char[] _chars = { 'ฒ', 'ฑ', 'ฐ' };
   private StringCollection _scTemp;

   /// <summary>
   /// Zwraca tablice zawierajaca klucz prywatny i publiczny
   /// Indexy: 0 = n, 1 = e, 2 = d
   /// </summary>
   public BigInt[] Keys
   {
        get
        {
            return new BigInt[]
            {
                 _n,
                 _e,
                 _d
             };
         }
   }
   #endregion

   /// <summary>
   /// Konstruktor przyjmujacy wartosci umozliwiajace szyfrowanie
   /// </summary>
   /// <param name="n">n</param>
   /// <param name="e">e</param>
   /// <param name="d">d</param>
   public Rsa(BigInt n, BigInt e, BigInt d)
   {
            _n = n;
            _e = e;
            _d = d;
   }

    /// <summary>
    /// Konstruktor tworzacy obiekt Rsa tylko do odszyfrowywania danych
    /// Przyjmuje pare tworzaca klucz prywatny
   /// </summary>
   /// <param name="n">n</param>
   /// <param name="d">d</param>
   public Rsa(BigInt n, BigInt d)
   {
         _n = n;
         _d = d;
   }

   #region ICipher Members
   /// <summary>
   /// Szyfruje podana kolekcje stringow
   /// </summary>
   /// <param name="sc">Kolekcja stringow</param>
   /// <returns>Kolekcja zaszyfrowanych stringow</returns>
   public StringCollection Encrypt(StringCollection sc)
   {
       StringCollection cipheredText = new StringCollection();
       _scTemp = null;

       foreach (string s in sc)
       {
            //Zamiana stringa na tablice znakow
            char[] charArray = s.ToCharArray();

            int index = 0;
            string str = string.Empty;

            //Pobranie zakodowanych znakow specjalnych
            BigInt[] biArray = GetCipheredSpecialChar();

            foreach (char ch in charArray)
            {
                //Wylosowanie znaku specjalnego wykorzystanego do szyfracji
                index = Randomizer.UniqueInstance.Next(0, biArray.Count());

                //Szyfrowanie
                BigInt c = BigInt.Parse(ch.GetNumberOfChar().ToString());
                c = c.FastModuloPower(_e, _n);

                //Znak + znak specjalny
                str += c.ToString() + biArray[index].ToString();
            }

            cipheredText.Add(str);
       }
       _scTemp = cipheredText;

       return cipheredText;
   }

   /// <summary>
   /// Szyfruje wskazana zawartosc pliku
   /// </summary>
   /// <param name="path">Sciezka do pliku</param>
   /// <returns>Kolekcja zaszyfrowana</returns>
   public StringCollection Encrypt(string path)
   {
        StringCollection sc = null;
        _scTemp = null;

        try
        {
            string[] stringArray = File.ReadAllLines(path, Encoding.Default);
            sc = new StringCollection();
            sc.AddRange(stringArray);
            sc = Encrypt(sc);
        }
        catch (IOException)
        {
            throw;
        }
        catch (Exception)
        {
            throw;
        }

        return sc;
   }

   /// <summary>
   /// Deszyfracja z kolekcji stringow
   /// </summary>
   /// <param name="sc">Kolekcja stringow</param>
   /// <returns>Zwraca rozszyfrowany text</returns>
   public StringCollection Decrypt(StringCollection sc)
   {
        _scTemp = null;
        //Konwersja zakodowanych znakow specjalnych
        //i wrzucenie wszytkich do tablicy string
        string[] cipheredSpecialCharArray = GetCipheredSpecialChar().ToList()
                     .ConvertAll<string>((BigInt bi) =>
                {
                    return bi.ToString();
                }).ToArray();

        StringCollection decodedTextCollection = new StringCollection();

        foreach (string s in sc)
        {
             //Podział zakodowanych znakow metoda Split wedlug znakow specjalnych
             List<string> cStringArray = s.Split(cipheredSpecialCharArray, StringSplitOptions.RemoveEmptyEntries).ToList();

             string decodedText = string.Empty;
             //Iteracja po tablicy zawierajacej poszczegolne znaki w postaci zakodwanych liczb
             foreach (string str in cStringArray)
             {
                 BigInt de = BigInt.Parse(str).FastModuloPower(_d, _n);
                 char c = de.GetCharFromNumber();
                 decodedText += c;
             }
             //Dodanie do kolekcji utworzonej linii textu
             decodedTextCollection.Add(decodedText);
        }

        _scTemp = decodedTextCollection;

        return decodedTextCollection;
   }

   /// <summary>
   /// Deszyfruje text odczytany z pliku
   /// </summary>
   /// <param name="path">Sciezka do pliku</param>
   /// <returns>Zwraca rozszyfrowany text</returns>
   public StringCollection Decrypt(string path)
   {
        _scTemp = null;

        StringCollection sc = null;
        try
        {
             string[] stringArray = File.ReadAllLines(path, Encoding.Default);
             sc = new StringCollection();
             sc.AddRange(stringArray);
             sc = Decrypt(sc);
        }
        catch (IOException)
        {
              throw;
        }
        catch (Exception)
        {
              throw;
        }

        return sc;
   }

   /// <summary>
   /// Metoda realizujaca zapis do pliku
   /// </summary>
   /// <param name="savePath">Sciezka do zapisu</param>
   /// <param name="sc">Kolekcja do zapisu</param>
   public void SaveToFile(string savePath)
   {
         if (!String.IsNullOrEmpty(savePath) && _scTemp != null)
         {
              StreamWriter writer = null;
              try
              {
                  foreach (string s in _scTemp)
                  {
                      if (writer == null)
                      {
                          writer = new StreamWriter(savePath, false, Encoding.UTF8);
                      }

                      writer.WriteLine(s);
                 }
             }
             catch (IOException)
             {
                   throw;
             }
             catch (Exception)
             {
                  throw;
             }
             finally
             {
                  if (writer != null)
                  {
                       writer.Close();
                  }
             }
        }
   }
    #endregion

    #region Help Methods

    /// <summary>
    /// Metoda umozliwiajaca zapis kluczy do pliku
    /// </summary>
    /// <param name="savePath">Sciezka do zapisu</param>
    public void SaveKeysToFile(string savePath)
    {
         StreamWriter writer = null;
         try
         {
             writer = new StreamWriter(savePath, false, Encoding.UTF8);
             writer.WriteLine("Klucz publiczny:");
             writer.WriteLine("KP: (" + _e.ToString() + "; "
                           + _n.ToString() + ")");
             writer.WriteLine("Klucz prywatny:");
             writer.WriteLine("KPr: ( d=" + _d.ToString() + "; n="
                           + _n.ToString() + ")");
         }
         catch (IOException)
         {
              throw;
         }
         catch (Exception)
         {
              throw;
         }
         finally
         {
                if (writer != null)
                {
                    writer.Close();
                }
         }
    }

    /// <summary>
    /// Metoda pomocnicza szyfrujaca kody znakow specjalnych
    /// </summary>
    /// <returns>Tablica kodow znakow specjalnych</returns>
    private BigInt[] GetCipheredSpecialChar()
    {
         BigInt[] biArray = new BigInt[_chars.Length];
         for (int i = 0; i < _chars.Length; i++)
         {
             biArray[i] = BigInt.Parse(_chars[i].GetNumberOfChar()
                                 .ToString()).FastModuloPower(_e, _n);
         }

         return biArray;
    }
    #endregion
}

Szkoła cz. 2 – Winner Takes All

Prowadzący zajęcia ze Sztucznej Inteligencji nie pozwala odpocząć moim szarym komórkom serwując kolejne zadanie z sieci neuronowych. Tym razem jednak nie musiałem rozwiązywać konkretnego problemu, a jedynie przedstawić działanie algorytmu.

Zadanie:

Napisać program prezentujący w sposób graficzny naukę neuronów stosując proces nauki bez nauczyciela. Uczenie przeprowadzić z wykorzystaniem jednego z algorytmów WTA (Winner Takes All) lub WTM (Winner Takes Most).

Krótkie postawy teoretyczne:

Nauka bez nauczyciela (sieć samoorganizująca się) – W tym procesie nauki pożądana odpowiedź sieci nie jest znana. Sieć nie posiadając informacji o poprawności danych powstałych na wyjściu, uczy się poprzez analizę pobudzenia, w trakcie tej analizy parametry sieci podlegają zmianom.

Nauka z algorytmem WTA:

  1. inicjalizacja wag sieci
    • neuron1 (w1, w2) = (1, 3)
    • neuron2 (w1, w2) = (2, 4)
  2. wzór na obliczenie odległości wektora wejściowego do wag każdego z neuronów:
    • d = sqrt((w1 – x1)^2 + (w2 – x2)^2)
  3. wybranie neuronu zwycięzcy (wygrywającego) dla którego odległość wag od wektora wejściowego jest najmniejsza (na podstawie Euklidesowej miary odległości).
    • Zwyciężył neuron2 ponieważ ma odległość mniejszą od neuron1.
  4. zmiana wartości poszczególnych wag tego neuronu przyjmując, że szybkość nauki wynosi n = 0,6
  5. powtórzenie kroków 2-5 dla wszystkich przykładów uczących.
  • Obliczenia (d):

    dn1 = sqrt((1 – 5)^2 + (3 – 8)^2) = sqrt(41) = 6,40

    dn2 = sqrt((2 – 5)^2 + (4 – 8)^ 2) = sqrt(25) = 5

  • Obliczenia (w):

    w1’ = w1 + n * (x1 – w1) = 2 + 0,6 * (5 – 2) = 3,8

    w2’ = w2 + n * (x2 – w2) = 4 + 0,6 * (8 – 4) = 6,4

Implementacja:

Cała samoorganizująca się sieć miała składać się jakby z dwóch części:

  1. Rodzin losowo wybranych punktów
  2. Losowo lub „ręcznie” dodanych neuronów

Postanowiłem więc utworzyć takie oto klasy reprezentujące powyższą sytuację:
Klasa „Characteristics”:

Klasa abstrakcyjna zawierająca właściwość zwracającą rodziny punktów, bądź utworzone neurony.
Zawiera także metodę Prepare() przygotowująca rodziny punktów, bądź neurony.

public abstract class Characteristics
{
        protected List<DataTable> _listOfAttribute;
        public abstract void Prepare();
}

Klasa „FamiliesOfPoints”:

Klasa dziedzicząca po „Characteristics”. Losuje rodziny punktów z wyznaczonego przedziału, oraz udostępnia je w postaci listy typu DataTable.

class FamiliesOfPoints : Characteristics
{
/// <summary>
/// Ilosc rodzin
/// </summary>
private int _amount;
/// <summary>
/// Obiekt pozwalajacy na losowanie liczb
/// </summary>
private Random _random;

/// <summary>
/// Wlasciwosc udostepniajaca obiekty
/// DataTable zawierajace rodziny punktow
/// </summary>
public List<DataTable> FamiliesPoints
{
  get
  {
    return base._listOfAttribute;
  }
}
/// <summary>
/// Konstruktor klasy
/// </summary>
/// <param name="amount">Ilosc rodzin punktow</param>
public FamiliesOfPoints(int amount)
{
base._listOfAttribute = new List<DataTable>();
_amount = amount;
_random = new Random(DateTime.Now.Millisecond);
}

/// <summary>
/// Metoda przygotowujaca rodziny punktow
/// Uruchamia losowanie
/// </summary>
public override void Prepare()
{
//Wylosowane liczby sa typu double
string switchOperand = "x1";
for (int i = 0; i < _amount; i++)
{
  //Wylosowanie czesci ulamkowej
  double doubleValue = RandomDoubleNumber();

  //Wylosowanie punktu i dodanie
  //czesci ulamkowej do niego
  double x2 = RandomIntNumber() + doubleValue;
  double x1 = RandomIntNumber() + doubleValue;

  //Utworzenie DataTable przechowujacego dana rodzine punktow
  DataTable dataTable = new DataTable("Family "
                       + i.ToString());
  dataTable.Columns.Add(new DataColumn("X1"));
  dataTable.Columns.Add(new DataColumn("X2"));

//Utworzenie punktow z danej rodziny na
//podstawie pierwszego wylosowanego punktu
//Przesuwam o wyznaczona wartosc punkt x1
//dodajac nowo wylosowana czesc ulamkowa lub x2
  switch (switchOperand)
  {
    case "x1":
     dataTable.Rows.Add(new object[] {
            Math.Round(i + RandomDoubleNumber(), Math.Round(x2, 2) });
     dataTable.Rows.Add(new object[] {
          Math.Round(i + RandomDoubleNumber() + 2, 2), Math.Round(x2 - 2, 2) });
     dataTable.Rows.Add(new object[] {
         Math.Round(i + RandomDoubleNumber() + 2, 1), Math.Round(x2 + 2, 1) });
     dataTable.Rows.Add(new object[] {
         Math.Round(i + RandomDoubleNumber() + 2, 2), Math.Round(x2, 2) });
     dataTable.Rows.Add(new object[] {
         Math.Round(i + RandomDoubleNumber() + 2, 1), Math.Round(x2, 1) });
     switchOperand = "x2";
    break;
	case "x2":
     dataTable.Rows.Add(new object[] { Math.Round(x1, 2),
				   Math.Round(x2, 2) });
	 dataTable.Rows.Add(new object[] { x1, Math.Round(x2 - 2, 2) });
     dataTable.Rows.Add(new object[] { x1, Math.Round(x2 + 2, 1) });
     dataTable.Rows.Add(new object[] { Math.Round(x1 + 2, 2),
	               Math.Round(x2 + 2, 2) });
     dataTable.Rows.Add(new object[] { Math.Round(x1 - 2, 1),
	               Math.Round(x2 - 2, 1) });
     switchOperand = "x1";
    break;
}
  _listOfAttribute.Add(dataTable);
}
}

/// <summary>
/// Metoda losujaca liczbe calkowita
/// </summary>
/// <returns>Zwraca wylosowana liczbe
/// w postaci typu double</returns>
private double RandomIntNumber()
{
  return _random.Next(3, 98);
}

/// <summary>
/// Losuje liczbe z przedzialu (0, 1)
/// </summary>
/// <returns>Zwraca wartosc liczby</returns>
private double RandomDoubleNumber()
{
  return Math.Round(_random.NextDouble(), 1);
}
}
}

Klasa „Neurons”:

Klasa dziedzicząca po „Characteristics”. Wagi neuronów pobierane są z pliku .xml lub losowane.

class Neurons : Characteristics
{
/// <summary>
/// Zmienna identyfikujaca sposob
/// tworzenia neuronow
/// odczyt z pliku/losowanie
/// </summary>
private object _neuronMode;

/// <summary>
/// Wlasciwosc zwracajaca obiekt
/// DataTable zawierajacy neurony
/// </summary>
public DataTable FamilyNeurons
{
  get
  {
    return base._listOfAttribute[0];
  }
}
/// <summary>
/// Szybkosc uczenia
/// </summary>
public double N
{
  get;
  private set;
}

/// <summary>
/// Konstruktor klasy
/// </summary>
/// <param name="neuronMode">Sposob tworzenia neuronow</param>
/// <param name="n">Szybkosc nauki</param>
public Neurons(object neuronMode, double n)
{
  base._listOfAttribute = new List<DataTable>();
  _neuronMode = neuronMode;
  N = n;
}
/// <summary>
/// Metoda przygotowujaca neurony
/// Kontroluje jakiego typu
//jest zmienna _neuronMode
/// Jesli jest to zmienna typu
///string przyjmuje to za sciezke do
/// pliku i uruchamia metode ReadFromFile()
/// Jesli jest to liczba typu int uruchamia metode
/// RandomNeurons()
/// </summary>
public override void Prepare()
{
  if (_neuronMode.GetType().Equals(typeof(string)))
  {
    ReadFromFile((string)_neuronMode);
  }

  if (_neuronMode.GetType().Equals(typeof(int)))
  {
    RandomNeurons();
  }
}
/// <summary>
/// Metoda odczytujaca z pliku,
// wagi poszczegolnych neuronow
/// </summary>
/// <param name="path">Sciezka do pliku</param>
private void ReadFromFile(string path)
{
  //Utworzenie obiektu DataTable
  //przechowujacego wagi
  //oraz identyfikator danego neuronu
  DataTable neuronsTable =
	new DataTable("Neurons");
  neuronsTable.Columns.Add(new DataColumn("Id"));
  neuronsTable.Columns.Add(new DataColumn("W1"));
  neuronsTable.Columns.Add(new DataColumn("W2"));

  //Utworzenie dokumentu XPathDocument pozwalajacego odczytac
  //plik xml z neuronami
  XPathDocument xPathDocument = new XPathDocument(path);
  int idNeuron = 0;
  XPathNavigator nav = xPathDocument.CreateNavigator();
  XPathNodeIterator iter = nav.Select("/Neurons/Neuron/*");

  object[] weights = new object[3];

  while (iter.MoveNext())
  {
    idNeuron++;
    weights[0] = idNeuron;
   //Sprawdzam czy aktualny node ma nazwe W1
    if (iter.Current.Name.Equals("W1"))
    {
      weights[1] = iter.Current.ValueAsDouble;
    }
    iter.MoveNext();
   //Sprawdzam czy aktualny node ma nazwe W2
    if (iter.Current.Name.Equals("W2"))
    {
      weights[2] = iter.Current.ValueAsDouble;
    }

   neuronsTable.Rows.Add(weights);
  }

  _listOfAttribute.Add(neuronsTable);
}

/// <summary>
/// Metoda losujaca wagi neuronow
/// </summary>
private void RandomNeurons()
{
  DataTable neuronsTable =
	new DataTable("Neurons");
  neuronsTable.Columns.Add(new DataColumn("Id"));
  neuronsTable.Columns.Add(new DataColumn("W1"));
  neuronsTable.Columns.Add(new DataColumn("W2"));

  //Utworzenie obiektu umozliwiajacego losowanie
  Random random =
	new Random(DateTime.Now.Millisecond);
 for (int i = 0; i < (int)_neuronMode; i++)
 {
  int w1 = random.Next(4, 97);
  int w2 = random.Next(4, 97);

  neuronsTable.Rows.Add(new object[] {
        i + 1,
         w1,
         w2 });
 }

  _listOfAttribute.Add(neuronsTable);
}
}

Plik xml zawierający wagi neuronów:

<?xml version="1.0" encoding="utf-8" ?>
<Neurons>
  <Neuron>
    <W1>1</W1>
    <W2>2</W2>
  </Neuron>
  <Neuron>
    <W1>2</W1>
    <W2>5</W2>
  </Neuron>
  <Neuron>
    <W1>4</W1>
    <W2>1</W2>
  </Neuron>
  <Neuron>
    <W1>7</W1>
    <W2>1</W2>
  </Neuron>
  <Neuron>
    <W1>4</W1>
    <W2>6</W2>
  </Neuron>
</Neurons>

Implementując dalej algorytm WTA postanowiłem poskładać sieć w całość wykorzystując wzorzec projektowy o nazwie „Builder” Wzorzec ten pozwolił mi oddzielić sposób tworzenia składników sieci i udostępnić obiekt reprezentujący algorytm WTA – złożony z tychże składników.

Interfejs „INetworkBuilder”:

W pierwszym kroku utworzyłem interfejs zlecający budowę konkretnych części danego algorytmu i zwracający utworzony obiekt z tych części.

interface INetworkBuilder
    {
        void BuildPointsClass(int amount);
        void BuildNeurons(object neuronMode, double n);
        Algorithm GetAlgorithm();
    }

Klasa „AlgorithmBuilderWTA”:

Następnie stworzyłem klasę implementująca ten interfejs – budującą zgodnie z wytycznymi interfejsu wybrany algorytm. (W tym przypadku algorytm WTA)

class AlgorithmBuilderWTA : INetworkBuilder
{
   private Algorithm _algorithmWTA = new AlgorithmWTA();

#region IBuilder Members
/// <summary>
/// Metoda uruchamiajac proces losowania punktow
/// </summary>
/// <param name="amount">Ilosc rodzin punktow</param>
public void BuildPointsClass(int amount)
{
  Characteristics familiesOfPoints =
  	new FamiliesOfPoints(amount);
  familiesOfPoints.Prepare();
  (_algorithmWTA as AlgorithmWTA).AddPartAlgorithm(familiesOfPoints);
}

/// <summary>
/// Metoda uruchamiajaca tworzenie neuronow
/// </summary>
/// <param name="neuronMode">Sposob tworzenie: odczyt z pliku/losowanie</param>
/// <param name="n">Szybkosc nauki</param>
public void BuildNeurons(object neuronMode, double n)
{
  Characteristics neurons =
		new Neurons(neuronMode, n);
  neurons.Prepare();
  (_algorithmWTA as AlgorithmWTA).AddPartAlgorithm(neurons);
}

/// <summary>
/// Metoda zwracajaca obiekt utworzonego algorytmu WTA
/// </summary>
/// <returns>Algorithm obiekt</returns>
public Algorithm GetAlgorithm()
{
	return _algorithmWTA;
}

#endregion
}
}

Kolejnym krokiem było stworzenie dwóch klas. Algorithm – niejako „spina” w jedną rodzinę klasy reprezentujące algorytmy nauki sieci (w moim przypadku mam tylko jeden algorytm, klasa nie zawiera żadnych składowych), oraz klasę AlgorithmWTA – oto ich kody.

Klasa „Algorithm”:

public abstract class Algorithm
{
}

Klasa „AlgorithmWTA”:

class AlgorithmWTA : Algorithm
{
#region Fields &amp;amp; Properties
/// <summary>
/// Obiekt rodziny punktow
/// </summary>
private FamiliesOfPoints _familiesPoints;
/// <summary>
/// Obiekt neuronow
/// </summary>
private Neurons _neurons;
/// <summary>
/// Wlasciwosc zwracajaca Neurony
/// </summary>
public DataTable FamilyNeurons
{
  get
  {
    return _neurons.FamilyNeurons;
  }
}
/// <summary>
/// Wlasciwosc zwracajaca rodziny punktow
/// </summary>
public List<DataTable> FamilyPoints
{
  get
  {
    return _familiesPoints.FamiliesPoints;
  }
}
/// <summary>
/// Wlasciwosc zwracajaca neurony nauczone
/// </summary>
public DataTable ChangedNeurons
{
  get;
  private set;
}
#endregion

#region Public Methods
/// <summary>
/// Metoda dodajaca obiekty: Kolejne skladowe
/// niezbedne do uruchomienia
/// procesu nauki algorytmem WTA
/// </summary>
/// <param name="part">Skladowe</param>
public void AddPartAlgorithm(Characteristics part)
{
  if (part.GetType().Equals(
           typeof(FamiliesOfPoints)))
  {
    _familiesPoints = (FamiliesOfPoints)part;
  }

  if (part.GetType().Equals(
           typeof(Neurons)))
  {
    _neurons = (Neurons)part;
  }
}
/// <summary>
/// Metoda uruchamiajaca proces nauki
/// </summary>
public void TeachNeurons()
{
if (_familiesPoints != null
		&amp;amp;&amp;amp; _neurons != null)
{
  int max = _familiesPoints.FamiliesPoints[0].Rows.Count;

  ChangedNeurons = GetNewNeuronsDataTable();

 foreach (DataRow row in _neurons.FamilyNeurons.Rows)
 {
   ChangedNeurons.Rows.Add(new object[] {
		row["Id"],
		row["W1"],
		row["W2"]});
 }

   DataRow winner = null;
 //Rozpoczecie procesu obliczania kolejnych
 //zwyciezcow dla danych wektorow uczacych
 //Petla zewnetrzna jest wykonywana tyle razy
 //ile wynosi ilosc najwiekszej rodziny punktow
 //W tym wypadku rodziny maja stala wielkosc 5 punktow
 for (int i = 0; i < max; i++)
 {
  //Petla wewnetrzna wybiera po jednym
  // punkcie z kazdej rodziny
  //Kolejno wyliczani sa zwyciezkie
  //neurony dla danego punktu
   for (int j = 0; j < _familiesPoints.FamiliesPoints.Count; j++)
   {
     DataTable dataTable = _familiesPoints.FamiliesPoints[j];

     if (max >= dataTable.Rows.Count)
     {
       max = dataTable.Rows.Count;
     }
   //Metoda ElementAtOrDefault(i) wyciaga po
   //jednym punkcie z kazdej rodziny
     DataRow point =
	    dataTable.Rows.OfType<DataRow>().ElementAtOrDefault(i);

     if (point != null)
     {
      //Obliczany jest zwyciezca
       winner = CalculateWinner(point);
     }

     if (winner != null)
     {
       CalculateWeights(point,
	         winner["Id"].ToString());
     }
   }
 }
}
}
#endregion

#region Help Methods
/// <summary>
/// Metoda obliczajaca zwyciezce dla
/// danego punku v i neuronow
/// </summary>
/// <param name="v">Punkt</param>
/// <param name="neurons">Neurony z aktualnymi wagami</param>
/// <returns>Zwyciezki neuron</returns>
private DataRow CalculateWinner(DataRow v)
{
  DataRow winner = null;
 if (v != null)
 {
    List<double> neuronsList =
		new List<double>();

    double valueOne;
    double valueTwo;

  for (int i = 0; i < ChangedNeurons.Rows.Count; i++)
  {
    valueOne = double.Parse(ChangedNeurons.Rows[i]["W1"].ToString())
	- double.Parse(v["X1"].ToString());
    valueTwo = double.Parse(ChangedNeurons.Rows[i]["W2"].ToString())
	- double.Parse(v["X2"].ToString());
    neuronsList.Add(Math.Round(Math.Sqrt(Math.Pow(valueOne, 2)
	+ Math.Pow(valueTwo, 2)), 2));
  }
  winner = ChangedNeurons.Rows.OfType<DataRow>()
     .ElementAtOrDefault(
		neuronsList.IndexOf(neuronsList.Min())
	);
 }

  return winner;
}
/// <summary>
/// Oblieczenie nowych wag dla danego neuronu
/// </summary>
/// <param name="neurons">Aktualne neurony</param>
/// <param name="v">Punkt</param>
/// <param name="idNeuron">Identyfikator neuronu
/// zapisany w DataTable</param>
private void CalculateWeights(DataRow v, string idNeuron)
{
  //Wyszukanie pierwszego neuronu zgodnego
  // z podanym Id w DataTable neurons
  DataRow neuron = ChangedNeurons.Rows.OfType<DataRow>()
	.FirstOrDefault(dr =>
  {
    return dr["Id"].Equals(idNeuron);
  });

 //Obliczenie nowych wag dla neuronow
 if (neuron != null)
 {
   neuron["W1"] = double.Parse(neuron["W1"].ToString())
   + _neurons.N * (double.Parse(v["X1"].ToString())
   - double.Parse(neuron["W1"].ToString()));

   neuron["W2"] = double.Parse(neuron["W2"].ToString())
   + _neurons.N *(double.Parse(v["X2"].ToString())
   - double.Parse(neuron["W2"].ToString()));
 }
}
/// <summary>
/// Utworzenie nowej tabeli dla neuronow
/// </summary>
/// <returns>Tabela</returns>
private DataTable GetNewNeuronsDataTable()
{
  var columnCollection = from colName
    in _neurons.FamilyNeurons.Columns.OfType<DataColumn>().AsEnumerable()
    select new DataColumn(colName.ColumnName);

  DataTable neurons = new DataTable();
  foreach (DataColumn column in columnCollection)
  {
    neurons.Columns.Add(column);
  }
    return neurons;
}
#endregion
}

Ostatnim etapem było utworzenie klasy uruchamiającej cały proces tworzenia.

Klasa „AlgorithmManager”:

class AlgorithmManager
{
/// <summary>
/// Metoda tworzaca algorytm WTA,
///przyjmujaca klase budujaca algorytm
/// oraz poszczegolne skladowe niezbedne
/// do utworznie algorytmu
/// </summary>
/// <param name="algorithmBuilder">Klasa budujaca algorytm</param>
/// <param name="amountOfPoints">Ilosc rodzin punktow</param>
/// <param name="neuronMode">Sposob tworzenia neuronow</param>
/// <param name="n">Szybkosc nauki</param>
public void BuildAlgorithm(INetworkBuilder algorithmBuilder, int amountOfPoints,
		object neuronMode, double n)
{
  algorithmBuilder.BuildPointsClass(amountOfPoints);
  algorithmBuilder.BuildNeurons(neuronMode, n);
}
}

Dodanie kolejnego algorytmu nauki sieci jest dość proste dzięki zastosowanemu wzorcowi – sprowadza się tylko do implementacji jednej klasy reprezentującej dany algorytm.

Przykładowe uruchomienie procesu nauki:
Ilość rodzin punktów: 5;
Neurony (Losowane) – ilość: 6;
Szybkość nauki n: 0,6.

INetworkBuilder algorithmBuilder = new AlgorithmBuilderWTA();

AlgorithmManager manager = new AlgorithmManager();
manager.BuildAlgorithm(algorithmBuilder, 5, 6, 0,6);
Algorithm algorithm = algorithmBuilder.GetAlgorithm();

AlgorithmWTA wta = (AlgorithmWTA)algorithm;
wta.TeachNeurons();

Na koniec zamieszczam screeny obrazujące wyniki działania algorytmu WTA.

Neurony pobrane z wpisanych wag w pliku xml:

xmlneurons.jpg

Neurony wylosowane:

losneurons.jpg

Szkoła cz.1

Kolejny semestr nauki przyniósł ze sobą dość interesujący przedmiot „Sztuczna Inteligencja”. Na pierwszym laboratorium otrzymałem proste zadanie rozgrzewkowe którego treść i rozwiązanie chciałbym zaprezentować. Pominę tutaj podstawy teoretyczne tego zadania, skupię się na implementacji – opisie klas które utworzyłem, pokaże także w jaki sposób narysowałem wymagany przez prowadzącego prosty wykres rozwiązania.

Na dobry początek treść zadania:

W ćwiczeniu tym będzie wykorzystany neuron do klasyfikacji samochodów. Neuron ”potrafi” ocenić dwie cechy samochodu: czy jest on „nowy” i czy jest „ładny”. Taki neuron będzie miał dwa wejścia
i jedno wyjście.

Sygnały wejściowe neuronu (podawane przez użytkownika programu, charakteryzują samochód):

  • wejście x1 – wiek samochodu
  • wejście x2 – wygląd samochodu

Informacje podawane na wejście neuronu (sygnały wejściowe) będą „mówiły” neuronowi o tym jaki samochód ”właśnie przejeżdża obok” – neuron wyrazi w odpowiedzi swoją o nim opinię. Wartości na wejściach będą oznaczały, że samochód jest: brzydki i stary (-4, -5), taki sobie (-1, 1), lub nowy i ładny (4, 5).

Wagi neuronu: w1, w2 (podawane przez użytkownika programu, należy przyjąć że ich wartość charakteryzuje komis).

Wagi neuronów to preferencje komisów wynikające m.in. stąd, że klienci danego komisu szukają w nim specyficznych aut. Np. komis sprzedawał do tej pory głównie samochody bardzo stare, przy czym analiza sprzedaży nie wykazała wpływu wyglądu na popyt auta w tym komisie. Komis ten jest reprezentowany przez neuron o wagach w1 =3, w2 = 0.

Schemat neuronu:

obliczenia.jpg

Zakładamy że wejścia oraz wagi są w przedziale: <-5; 5>
Odpowiedź neuronu jest poziomem zadowolenia komisu
z dostarczonego auta. Im wartość jest większa tym poziom zadowolenia jest wyższy.

Polecenia:

  1. Napisz program który generuje określoną przez użytkownika liczbe komisów od 2-5 (z możliwością określenia preferencji komisów „wag” ) oraz automatycznie przydziela auta do komisu
    po wpisaniu x1 i x2.
  2. Wygeneruj samochody o wszystkich możliwych x1 i x2
    z przedziału <-5,5> (jest ich łącznie 121) i przydziel wygenerowane samochody do komisów.
    (narysuj wykres prezentujący przydział samochodów do poszczególnych komisów)

Moje rozwiązanie

Przykład:
Charakterystyka samochodu:

  • x1 = -4 (wiek samochodu)
  • x2 = -5 (wygląd samochodu)

Generuje dwa komisy o wagach:

Komis nr 1:

  • w1 = 3
  • w2 = 0

Komis nr 2:

  • w1 = – 3
  • w2 = 1

Obliczenie poziomu zadowolenia jednego z komisów (Komis nr 1):

neuron.jpg

Poziom zadowolenia Komisu nr 1 wynosi: -12, analogicznie możemy obliczyć poziom zadowolenia z dostarczonego auta Komisu nr 2 który wynosi: 7. Komis o wyższym poziomie zadowolenia przyjmuje auto w tym wypadku jest to Komis nr 2.

Teraz to co Tygryski lubią najbardziej ;) – Implementacja:

Ograniczę się tutaj do pokazania kodu najważniejszych klas. W swoim programie wykorzystałem wzorzec projektowy „Decorator”. Samochód jaki i komis charakteryzują po dwie właściwości, więc w pierwszej kolejności utworzyłem klasę abstrakcyjną uogólniająca ten fakt:

public abstract class Characteristic
    {
        #region Abstract
        /// <summary>
        /// Preferencja pierwsza
        /// </summary>
        public abstract int PreferenceOne { get; protected set; }
        /// <summary>
        /// Preferencja druga
        /// </summary>
        public abstract int PreferenceTwo { get; protected set; }
        #endregion
    }

Następnie utworzyłem klasy: Car oraz CarCommission dziedziczące po klasie abstrakcyjnej Characteristic. Klasa Car jest w tym momencie komponentem który będzie „dekorowany” przez klase CarCommission. Dodatkowo klasa Car dziedziczy po interfejsie IEnumerable<Car> co umożliwia mi utworzenie kolekcji obiektów klasy Car i iteracji po niej. Wewnątrz klasy znajduje się lista List&ltCar&gt która przechowuję wszystkie kombinację aut typu wiek/wygląd. (Polecenie II). Natomiast klasa CarCommission jest odzwierciedleniem komisu samochodowego i określa poziom zadowolenia z dostarczonego auta dzięki metodzie Satisfaction().

Klasa Car:

///Komponent
public class Car : Characteristic, IEnumerable<Car>
    {
        private List<Car> _cars;
        /// <summary>
        /// Konstruktor klasy okreslajacy wiek i wyglad samochodu
        /// </summary>
        /// <param name="x1">Wiek samochodu</param>
        /// <param name="x2">Wyglad samochodu</param>
        public Car(int x1, int x2)
        {
            PreferenceOne = x1;
            PreferenceTwo = x2;
        }

        /// <summary>
        /// Konstruktor tworzacy liste ze wszystkimi
        /// kombinacjami opisujacymi samochod wyglad/wiek
        /// </summary>
        public Car()
        {
            _cars = new List<Car>();
        }

        #region Override region
        /// <summary>
        /// Wlasciwosc zwracajaca wiek samochodu
        /// </summary>
        public override int CharacterOne
        {
            get;
            protected set;
        }

        /// <summary>
        /// Wlasciwosc zwracajaca wyglad samochodu
        /// </summary>
        public override int PreferenceTwo
        {
            get;
            protected set;
        }

        public override string ToString()
        {
            return "(" + PreferenceOne.ToString() + ", "
                     + PreferenceTwo.ToString() + ")";
        }
        #endregion

        #region IEnumerable<Car> Members

        public IEnumerator<Car> GetEnumerator()
        {
            return _cars.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator
                System.Collections.IEnumerable.GetEnumerator()
        {
            return _cars.GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Dodaje samochod do listy
        /// </summary>
        /// <param name="car">Obiekt samochodu</param>
        public void AddCar(Car car)
        {
            if (_cars != null)
            {
                _cars.Add(car);
            }
        }
    }

Klasa CarCommission

 public class CarCommission : Characteristic
    {
        private Characteristic _car;
        private string _commissionName;

        /// <summary>
        /// Konstruktor klasy przyjmujacy samochod do komisu
        /// Okreslajacy wagi
        /// Nazwe komisu
        /// </summary>
        /// <param name="car">Samochod</param>
        /// <param name="w1">Waga pierwsza</param>
        /// <param name="w2">Waga druga</param>
        /// <param name="commissonName">Nazwa komisu</param>
        public CarCommission(Characteristic car, int w1,
                     int w2, string commissionName)
        {
            _car = car;
            PreferenceOne = w1;
            PreferenceTwo = w2;
            _commissionName = commissionName;
        }

        #region Override region
        /// <summary>
        /// Waga pierwsza W1
        /// </summary>
        public override int CharacterOne
        {
            get;
            protected set;
        }

        /// <summary>
        /// Waga druga W2
        /// </summary>
        public override int PreferenceTwo
        {
            get;
            protected set;
        }

        public override string ToString()
        {
            return _commissionName + ": " + Satisfaction().ToString();
        }
        #endregion

        /// <summary>
        /// Poziom zadowolenia z pozyskanego samochodu
        /// </summary>
        /// <returns>Zadowolenie komisu z samochodu</returns>
        public int Satisfaction()
        {
            return _car.PreferenceOne * PreferenceOne +
                     _car.PreferenceTwo * PreferenceTwo;
        }
    }

Przykład oblczenia poziomu zadowolenia jednego z komisow z dostarczonego auta (pseudo kod):

int x1 = -4;
int x2 = -5;
///Tworze obiekt samochodu i charakteryzuje
///go wykorzystujac odpowiedni konstruktor klasy Car
Car car = new Car(x1, x2);
int w1 = 3;
int w2 = 0;
///Dostarczam samochod do komisu,
///charakteryzuje komis podajac jego wagi i nazwe
CarCommission carCommission =
          new CarCommission(car, w1, w2, "Komis 1");
//Komis okresla swoj poziom zadowolenia z dostarczonego auta
int satisfaction = carCommission.Satisfaction();

Samochód trafia do komisu o najwyższym poziomie zadowolenia. Druga część zadania opiera się na tych kilku wyżej napisanych linijkach, krótki opis:
Dostarczam do obiektu klasy Car wszystkie auta z przedziału <-5; 5> (np. (-5, 4), (-5, 4) itd.) jest ich dokładnie 121. Następnie iteruję po obiekcie tej klasy (umożliwia mi to implementacja interfejsu IEnumerable<Car> (patrz kod klasy Car) dostaczając kolejne auta do wygenerowanych komisów. Spradzam który z komisów jest najbardziej zadowolony i przypisuję mu auto.

Ostatnim etapem zadania było narysowanie wykresu. Wykorzystałem tutaj darmową bibliotekę ZedGraph dostępną tutaj. Ściągnięty plik .dll najlepiej dodać do Toolbox-a wybierając menu Tools —> Choose Toolbox Items —-> Browse (wskazać ZedGraph.dll)
dodaną kontrolke przenieść na formularz. Kod rysujący wykres do powyższego zadania przedstawia się następująco:

            PointPairList pointPairList = new PointPairList();
            //samochod (-1, 5)
            pointPairList.Add(-1, 5);
            //samochod (-1, 4)
            pointPairList.Add(-1, 4);
            //samochod (-2, 4)
            pointPairList.Add(-2, 4);

            //pobieramy obiekt GraphPane na ktorym
            //zostanie narysowany wykres z kontrolki ZedGraph
            GraphPane graphPane = zedGraphControl.GraphPane;
            //Ustawiamy tytul wykresu
            zedGraphControl.GraphPane.Title.Text = "Wykres";
            //Opis osi x
            graphPane.XAxis.Title.Text = "Wygląd samochodu.";
            //Opis osi y
            graphPane.YAxis.Title.Text = "Wiek samochodu.";

            //Rysujemy wykres, dostarczajac do metody kolejno:
            //Nazwe komisu, pary punktow(samochody), kolor linii
            //laczacej punkty, ksztalt punktow
            graphPane.AddCurve("Komis 1", pointPairList,
                  Color.Red, SymbolType.Circle);

            //Wywolujemy metode wykonujaca rysowanie
            zedGraphControl.AxisChange();

Zrzut ekranu prezentujący otrzymane wykresy dla dwóch komisów o wyżej wymienionych wagach:

wykres.jpg

Moje zmagania z programem zakończyły się pełnym sukcesem. ;)