15/07/2013

Quiz - coś do poduszki

Home

W poprzednim poście napisałem o zorganizowanym przez siebie Quiz'ie wiedzy o .NET i szeroko pojętym programowaniu. Jeden z czytelników poprosił o udostępnienie pytań i odpowiedzi. Nie zrobiłem tego od razu ale w ostatni weekend znalazłem chwilę czasu na zebranie wszystkich pytań/odpowiedzi do kupy, ujednolicenie formatowania itp. Dokument w formacie PDF dostępny jest na moim dysku SkyDrive:



Przy okazji będę wdzięczny ze wszelkie komentarze. Czy treść pytań jest zrozumiała? Czy pytania były łatwe, trudne, a może w sam raz? Czy ich tematyka podoba się Wam? Czy wzięlibyście udział w takim Quiz'ie? Zwróćcie również uwagę na pytanie 23 przy którym niestety zaliczyłem wpadkę i zdecydowałem się je anulować. Jeśli ktoś z Was potrafi wyjaśnić moje wątpliwości zapraszam do komentowania.

07/07/2013

TPL Dataflow + problem filozofów

Home

Jakiś czas temu na blogu Piotrka Zielińskiego przeczytałem o TPL Dataflow Library czyli o bibliotece dostarczającej komponentów ułatwiających komunikację (przekazywanie danych) w środowisku wielowątkowym. Temat mnie zaciekawił i postanowiłem trochę pobawić się z tą technologią. Na tapecie nie miałem żadnego "prawdziwego" projektu, w którym dałoby się wykorzystać nową zabawkę, postanowiłem więc wykonać ćwiczenie umysłowe i rozwiązać klasyczny problem pięciu filozofów z użyciem TPL Dataflow.

W moim rozwiązaniu każda pojedyncza pałeczka do jedzenia ryżu reprezentowana jest przez instancję klasy BufferBlock<T&gt gdzie T to w tym przypadku klasa Chopstick (klasa wydmuszka, nie zawiera żadnych właściwości ani metod). BufferedBlock<T>to nic innego jak kolejka FIFO, która może mieć wielu czytelników i wielu zapisujących.

Filozof potrzebuje jednak dwóch pałeczek aby rozpocząć jedzenie. Aby spełnić to wymaganie używam klasy JoinBlock<T,Z> gdzie T i Z do znowu klasa Chopstick. JoinBlock działa w ten sposób, ze monitoruje dwa źródła danych i jeśli w obu źródłach równocześnie są dane to grupuje je i wysyła do słuchacza. W tym przypadku JoinBlock czeka na dwie wolne pałeczki.
var chopsticks = new JoinBlock<Chopstick, Chopstick>(new GroupingDataflowBlockOptions { MaxNumberOfGroups = 1 });

_left.LinkTo(chopsticks.Target1);
_right.LinkTo(chopsticks.Target2);

_chopsticks = chopsticks.Receive();
Ustawienie właściwości MaxNumberOfGroups jest konieczne, aby blok odczytał tylko dwa komunikaty. Odłożenie pałeczek na stół jest natomiast równoważne z wysłaniem komunikatu (pałeczki) z powrotem do bufora tak, aby oczekujący na nie filozofowie mogli rozpocząć jedzenie.
_left.SendAsync(_chopsticks.Item1);
_right.SendAsync(_chopsticks.Item2);
Do tego, aby filozofowie mogli informować świat zewnętrzny o tym, co robią, również użyłem klasy BufferBlock<T>. Za każdym razem kiedy jeden z filozofów kończy/rozpoczyna jedzenie wysyła komunikat ze swoim aktualnym stanem. Ja napisałem prostą aplikację w WinForms, która nasłuchuje na te komunikaty i odpowiednio uaktualnia UI.
private readonly BufferBlock<PhilosopherState> _philosophersState = new BufferBlock<PhilosopherState>();
...
_philosophersState.LinkTo(new ActionBlock<PhilosopherState>(state => UpdateState(state)), new DataflowLinkOptions());
Każdy filozof modelowany jest przez instancję klasy Philosopher i działa w swoim własnym wątku. Co jakiś losowy czas decyduje, co robić dalej tj.: kontynuować myślenie/jedzenie czy rozpocząć myślenie/jedzenie. Kiedy zbierzemy to wszystko do kupy, otrzymamy następujący kod.

Pokaż/Ukryj kod klasy Philosopher
namespace PhilosopherProblemWithDataFlows
{
    public class Philosopher
    {
        private const int SleepTime = 100;

        private readonly int _index;
        private readonly BufferBlock<Chopstick> _left;
        private readonly BufferBlock<Chopstick> _right;
        private readonly BufferBlock<PhilosopherState> _philosophersState;

        private bool _goHome;
        private Tuple<Chopstick, Chopstick> _chopsticks;

        public Philosopher(int index, BufferBlock<Chopstick> left, BufferBlock<Chopstick> right, BufferBlock<PhilosopherState> philosophersState)
        {
            _index = index;
            _left = left;
            _right = right;
            _philosophersState = philosophersState;
        }

        public void TakeASeat()
        {
            var rand = new Random((int)DateTime.Now.Ticks);

            while (true)
            {
                if (_goHome)
                {
                    PutChopsticks();                
                    return;
                }

                if (rand.Next() % 2 == 0)
                    Eat();
                else
                    Think();

                Thread.Sleep((rand.Next(10) + 1) * SleepTime);
            }
        }

        public void GoHome()
        {
            _goHome = true;
        }

        private void Eat()
        {
            if (_chopsticks == null)
            {
                var chopsticks =
                    new JoinBlock<Chopstick, Chopstick >(new GroupingDataflowBlockOptions { MaxNumberOfGroups  = 1 });

                _left.LinkTo(chopsticks.Target1);
                _right.LinkTo(chopsticks.Target2);

                _chopsticks = chopsticks.Receive();
                chopsticks.Complete();
            }

            _philosophersState.SendAsync(new PhilosopherState { Index = _index,  IsEating = true });
        }

        private void Think()
        {
            PutChopsticks();

            _philosophersState.SendAsync(new PhilosopherState { Index = _index,  IsEating = false});
        }

        private void PutChopsticks()
        {
            if (_chopsticks != null)
            {
                _left.SendAsync(_chopsticks.Item1);
                _right.SendAsync(_chopsticks.Item2);
                _chopsticks = null;
            }
        }
    }

    public class Chopstick
    {
    }

    public class PhilosopherState
    {
        public int Index { get; set; }
        public bool IsEating { get; set; }
    }
}
Pokaż/Ukryj kod okna Win Forms
namespace PhilosopherProblemWithDataFlows
{
    public partial class Form1 : Form
    {
        private readonly Color EatingColor = Color.Red;
        private readonly Color ThinkingColor = Color.Green;

        private readonly List<Label> _stateLabels = new List<Label>();
        private readonly List<Philosopher> _philosophers = new List<Philosopher>();
        private readonly BufferBlock<PhilosopherState> _philosophersState = new BufferBlock<PhilosopherState>();

        public Form1()
        {
            InitializeComponent();
            Closing += (sender, args) =>
                {
                    _philosophersState.Complete();
                    _philosophers.ForEach(p => p.GoHome());
                };

            _stateLabels.Add(philosopher1);
            _stateLabels.Add(philosopher2);
            _stateLabels.Add(philosopher3);
            _stateLabels.Add(philosopher4);
            _stateLabels.Add(philosopher5);
            _stateLabels.ForEach(l => l.BackColor = ThinkingColor);
            
            Start();
        }

        private void Start()
        {
            _philosophersState.LinkTo(new ActionBlock<PhilosopherState>(state => UpdateState(state)), new DataflowLinkOptions());

            var chopsticks = new[]
                {
                    new BufferBlock<Chopstick>(),
                    new BufferBlock<Chopstick>(),
                    new BufferBlock<Chopstick>(),
                    new BufferBlock<Chopstick>(),
                    new BufferBlock<Chopstick>()
                };

            foreach (var ch in chopsticks)
                ch.Post(new Chopstick());

            for (var i = 0; i < 5; ++i)
                _philosophers.Add(new Philosopher(
                            i,
                            chopsticks[i],
                            chopsticks[(i + 1) % 5],
                            philosophersState));

            for (var i = 0; i < 5; ++i)
            {
                var th = new Thread(_philosophers[i].TakeASeat);
                th.Start();
            }
        }

        private void UpdateState(PhilosopherState state)
        {
            var label = _stateLabels[state.Index];
            label.Invoke((MethodInvoker)delegate { label.BackColor = state.IsEating ? EatingColor : ThinkingColor; });
        }
    }
}

Kod designer'a pominąłem bo jest trywialny i zawiera tylko 5 etykiet o nazwach philosopher1, philosopher2 itd.

Na koniec mała zagadka. Moja implementacja zawiera pewne uproszczenie oryginalnego problemu 5 ucztujących filozofów. Jakie?

06/07/2013

Quiz - rozruszaj towarzystwo

Home

Czy Wasza praca jest zawsze ciekawa i pełna wyznań, a może czasami Was nuży i zastanawiacie się jak ją urozmaicić?

Byłoby super gdyby każdy z nas mógł odpowiedzieć na to pytanie "Tak moja praca jest bardzo ciekawa i nigdy mnie nie nuży", ale w rzeczywistości bywa różnie. Zawsze można zmienić pracę, ale można również próbować zmienić coś w obecnej. Sceptycy pewnie powiedzą, a co ja mały trybik w wielkiej machinie mogę zrobić. Dużo zależy od firmy i bywają przypadki beznadziejne. Ja na rozruszanie zespołu proponuję zorganizować Quiz.

Ja postanowiłem coś takiego zrobić już po raz drugi w swojej karierze i jestem bardzo zadowolony z efektów. Odgórnym celem Quiz'u była zabawa i nauka. Zasady były proste:
  • 10 tur po 3 pytania (łatwe, średnie i trudne) dotyczące szeroko pojętego programowania.
  • Pytania przygotowałem za wczasu, tak aby nie musieć ich wymyślać co tydzień.
  • Starałem się, aby odpowiedzi można było udzielić w jednym zdaniu, czasem wręcz jednym słowem, ewentualnie napisać kilka linijek kodu.
  • Pytania rozsyłałem w poniedziałek rano i czekałem na odpowiedzi do końca dnia. Następnego dnia oceniałem wyniki, rozsyłałem odpowiedzi z komentarzami i aktualną statystykę (anonimowa lista).
  • Udział był dobrowolny.
  • Udział był anonimowy, każdy uczestnik miał przypisany identyfikator i tylko ja znałem mapowanie.
  • Zwycięzca mógł wybrać sobie nagrodę ufundowaną przez firmę (wybrał przenośny dysk).
Do Quiz'u zgłosiło się 20 osób. Łącznie zadałem 9 łatwych pytań, 12 średnich oraz 9 trudnych za łącznie 60 punktów. Zwycięzca zdobył 52.5 punkty i walka trwała do ostatniej tury ponieważ druga osoba w rankingu miała 52 punkty, a trzecia i czwarta 51. Zebrane w całości pytania i odpowiedzi utworzyły 22 stronicowy dokument!

Quiz spodobał się i spotkał się z bardzo dobrym odbiorem również ze strony przełożonego, który dał pieniądze na nagrodę. Najbardziej cieszy mnie jednak to, że już kilkukrotnie słyszałem od kolegów, że w trakcie Quiz'u nauczyli się czegoś nowego.

Organizacja Quiz'u to również nauka dla mnie. Aby przygotować pytania i nie zaliczyć wpadki wiele zagadnień musiałem przestudiować dużo dokładniej. Nauczyłem się również jak, wbrew pozorom, trudne jest układanie pytań w taki sposób, aby były zrozumiałe dla wszystkich. Pomimo, że każde zadanie czytałem wielokrotnie zdarzało się, że musiałem odpowiadać na pytania i rozsyłać dodatkowe informacje.

Na koniec kilka przykładów pytań:

Łatwe

We have to measure an execution time with the maximum possible resolution/precision. It was implemented in the following way. Does this code meet this requirement? If yes, why? If not, propose a better solution.
var start = DateTime.Now;
//...
var end = DateTime.Now;
Średnie

.NET 4.5 introduces a new, easy and elegant way to retrieve a name of a method (caller) which executed/called a given method (calle).
public void Fun()
{
//Here, I want to get a name of a method that executed/called Fun method
}
Trudne

We want to use a class called  Fun, that  is defined in an external library. Unfortunately, a constructor of this class contains a bug which causes an exception. In other words, we can't create a new instance of Fun class, but we have to. You have to find a workaround. You mustn't modify Fun class.

08/06/2013

Project Euler

Home

Po raz pierwszy o Project Euler usłyszałem już tak dawno temu, że nie pamiętam kiedy ale dopiero ostatnio założyłem konto i rozpocząłem zabawę. W skrócie jest to zestaw zadań matematyczno programistycznych (obecnie ponad 400) o różnym stopniu złożoności i trudności. Rozwiązanie zadania polega na podaniu zawsze jednej liczby np.: liczba cyklicznych liczb pierwszych poniżej 1 miliona. Część problemów można rozwiązać siłowo ale nim dalej w las tym trudniej i trzeba kombinować.

Do założenia konta zachęcił mnie kolega z pracy (Dzięki Piotrek!). Okazało się, że w zabawie bierze udział już kilku z nas. Witryna projektu umożliwia śledzenie postępów innych, dyskusję na temat problemów, podaje statystyki ile osób rozwiązało poszczególne zadania itd.

Do tej pory korzystałem z konkurencyjnego portalu TopCoder. Co przyciągnęło mnie do Project Euler? Sądzę, że kilka rzeczy:
  • Proste reguły zabawy.
  • Bardzo prosty interfejs.
  • Element społecznościowy.
  • Stopniowanie trudności. Można zacząć od bardzo prostych problemów i przechodzić do coraz trudniejszych.
  • Krótkie i zwięzłe zadania, co nie znaczy, że zawsze proste.
  • To coś.
Nie mówię, że TopCoder jest gorszy. Jest po prostu innych. Dla mnie Project Euler to czysta, nieskomplikowana zabawa. TopCoder ma wyższy próg wejścia ale z drugiej strony daje dużo więcej np.: nagrody pieniężne.

Podsumowując, jeśli jeszcze nie próbowaliście to zachęcam. Ja spróbowałem i nie mogę się oderwać.

05/06/2013

Microsoft.Office.Interop

Home

Będzie krótko i zwięźle, temat nie jest pasjonujący ale może komuś oszczędzi trochę nerwów. Ostatnio zdarzyło mi się pracować z kodem używającym Microsoft.Office.Interop do generacji dokumentów programu Word. Kod do najpiękniejszych nie należał ale najwięcej problemów miałem z interoperacyjnością.

Pierwszy błąd napotkałem po otwarciu dokumentu i próbie ustawienia właściwości MailMerge.MainDocumentType lub jak się później okazało dowolnej innej. Ten błąd to:

This command is not available because no document is open.

Przyczyną błędu okazał się brak katalogu C:\Windows\System32\config\systemprofile\Desktop. W moim przypadku po prostu skopiowałem analogiczny katalog C:\Windows\SysWOW64\config\systemprofile\Desktop i błąd przestał występować. Drugi błąd jaki napotkałem to:

Programmatic access to Visual Basic Project is not trusted.

Na początku spróbowałem więc zaznaczyć odpowiednią opcję w programie Word:

Word Options -> Trust Center -> Trust Center Settings -> Trust access to the VBA project object model

Niestety ale to nie pomogło. Skoro nie można po dobroci to skorzystałem z rozwiązanie "siłowego" i dodałem odpowiedni klucz do rejestru:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Word\Security]
"AccessVBOM"=dword:00000001


Problemy te zaobserwowałem na Windows 7 Professional dla Microsoft Word 2010.