2009
04.27

Ostatnio przy implementacji aplikacji dla platformy Mediaroom, napotkałem na problem ustalania podzbioru pewnego danego zbioru napisów, który to podzbiór miałby zawierać wszystkie stringi rozpoczynające się ustalonym prefiksem. Jak nietrudno się domyśleć, procedura potrzebna mi była do implementacji listy podpowiedzi – użytkownik rozpoczyna wprowadzanie tekstu, a na ekranie pojawia mu się lista podpowiedzi – scenariusz szeroko stosowany we współczesnym webie. W aplikacjach dla telewizji ma on jednak o wiele większe znaczenie, bo możliwości wprowadzania tekstu za pomocą pilota są raczej ograniczone (stosuje się metodę triple-tap znaną z komórek).

Problem przedstawia się zatem następująco: mając zadaną tablicę ze stringami (lista wszystkich możliwych sugestii), dla określonego prefiksu (ciągu znaków), podać listę tych stringów z tablicy wejściowej, które rozpoczynają się od prefiksu.

Trywialne rozwiązanie:

//dane wejściowe
String[] input = strings.ToArray();

//prefiks, dla którego szukamy podzbioru
String prefix = "ab";

String[] subset = Array.FindAll<String>(
    input,
    new Predicate<string>(s => s.StartsWith(prefix))
    );

ma jedną zaletę – zajmuje mało kodu. Niestety poza tym ma same wady:

  1. Czas ustalenia interesującego nas podzbioru zależy liniowo od wielkości danych wejściowych (liczby stringów w tablicy). Przy, powiedzmy, 50 tysiącach stringów użytkownik zauważalnie długo czeka na podpowiedź.
  2. Łatwo zauważyć, że dla każdej kolejnej wpisanej litery prefiksu, zawężamy wynik poszukiwań – dla n-tej litery prefiksu wynikiem jest podzbiór uzyskany w poprzednim kroku, dla n-1 litery. Trywialna metoda nie korzysta jednak z tej obserwacji i dla każdej litery proces poszukiwania zaczynany jest na nowo.

Z pomocą przychodzą drzewa trie (czyt. traj). Trie jest drzewem poszukiwań, którego wierzchołki są oznaczone kolejnymi prefiksami słów z listy. Prócz prefiksu wierzchołek zawiera informacje o słowach z listy, które “pasują” do tego prefiksu. Oczywiście w celach optymalizacji, w drzewie nie są przechowywane same podzbiory wyjściowego zbioru, tylko indeks początkowy i końcowy danego podzbioru.

Przykład:

Trie - przykład

Trie - przykład

Powyższe drzewo trie zawiera dane o prefiksach dla następującej listy słów:

  • AR
  • AS
  • B
  • DO
  • ELA
  • ELF
  • EM

Żeby otrzymać informację o podzbiorze stringów zaczynających się od prefiksu “EL” należy przejść od korzenia przez węzeł “E” aż do węzła “L” – otrzymujemy indeksy 4-5, czyli słowa “ELA” i “ELF”. Warto zauważyć, że koszt wyszukania interesującego nas podzbioru nie zależy teraz od wielkości danych wejściowych.

Zanim przystąpimy do napisania funkcji budującej drzewo, zdefiniujmy najpierw dane pomocnicze: strukturę Prefix, i klasę TreeNode<T>.

Prefix:

public struct Prefix
{
    int _start;
    public int Start
    {
        get { return _start; }
        set { _start = value; }
    }

    int _end;
    public int End
    {
        get { return _end; }
        set { _end = value; }
    }

    char _letter;
    public char Letter
    {
        get { return _letter; }
        set { _letter = value; }
    }

    public Prefix(int start, int end, char letter)
    {
        _start = start;
        _end = end;
        _letter = letter;
    }
}

TreeNode:

public class TreeNode<T>
{
    private TreeNode _parent = null;
    protected List<TreeNode> _children = new List<TreeNode>();
    private T _value;

    public void AddChild(TreeNode child)
    {
        if (!_children.Contains(child))
        {
            _children.Add(child);
            child._parent = this;
        }
    }

    public TreeNode Parent
    {
        get { return _parent; }
        set { _parent = value; }
    }

    public TreeNode(T value)
    {
        this._value = value;
    }

    public TreeNode(T value, TreeNode[] children)
    {
        new TreeNode(value);
        _children = new List<TreeNode>(children);
    }

    public T Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public List<TreeNode> Children
    {
        get { return _children; }
    }
}

Powyższa klasa jest bardzo prostą implementacją węzła drzewa przechowującego zadany typ danych o zbiorze synów trzymanych za pomocą listy generycznej.

Mając tak zdefiniowane klasy pomocnicze, możemy napisać rekurencyjną metodę tworzącą drzewo trie:

public class Trie
{
    private String[] _input;
    private TreeNode<Prefix> _tree;
    private char[] _availableChars;

    private void MakeNode(TreeNode<Prefix> root, int indexOfString)
    {
        int wordIndex = root.Value.Start;
        int lastIndex = wordIndex;
        bool nodeStarted = false;
        char character = '';
        char prevChar = character;
        while (wordIndex <= root.Value.End)
        {
            String s = _input[wordIndex];
            prevChar = character;
            if (indexOfString < s.Length)
            {
                if (s[indexOfString] != character)
                {
                    character = s[indexOfString];
                    if (nodeStarted)
                    {
                        Prefix newPair = new Prefix(lastIndex, wordIndex - 1, prevChar);
                        lastIndex = wordIndex;
                        TreeNode<Prefix> newNode = new TreeNode<Prefix>(newPair);
                        root.AddChild(newNode);
                        nodeStarted = false;
                    }
                }
                else
                {
                    nodeStarted = true;
                    wordIndex++;
                }
            }
            else
            {
                wordIndex++;
            }
        }
        if (nodeStarted)
        {
            root.AddChild(new TreeNode<Prefix>(new Prefix(lastIndex, root.Value.End, character)));
        }
        foreach (TreeNode<Prefix> child in root.Children)
        {
            MakeNode(child, indexOfString + 1);
        }
    }

    public Trie(String[] input)
    {
        _input = input;
        _tree = new TreeNode<Prefix>(new Prefix(0, _input.Length - 1, ''));
        MakeNode(_tree, 0);
    }
}

Kluczowa jest metoda MakeNode, która w sposób rekurencyjny tworzy listę synów dla istniejących wierzchołków drzewa trie, w oparciu o drugi parametr, który określa indeks bieżącej litery w szukanych stringach.

Mając utworzone drzewo trie dla danego zbioru stringów, możemy wywoływać następującą metodę z klasy Trie:

        public String[] GetStringsForPrefix(string prefix)
        {
            TreeNode<Prefix> current = _tree;
            for (int i = 0; i < prefix.Length; i++)
            {
                current = current.Children.Find(new Predicate<TreeNode<Prefix>>(t => t.Value.Letter == prefix[i]));
                if (current == null)
                {
                    return null;
                }
            }
            int length = current.Value.End - current.Value.Start + 1;
            string[] result = new string[length];
            Array.Copy(_input, current.Value.Start, result, 0, length);
            return result;
        }

która zwróci nam interesujący nas podzbiór stringów. W ten sposób mamy rozwiązanie początkowego problemu.

Powyższa implementacja struktury danych trie z całą pewnością może być bardziej optymalna – chciałem jednak zachować czytelność kodu i zaprezentować ogólną ideę tworzenia drzewa trie, a nie skupiać się na optymalizacji.

Wadą drzew trie jest ich stosunkowo duży rozmiar w pamięci (jak duży? od czego zależy? czy można go zmniejszyć?). Można to poprawić stosując tzw. drzewa Patricia, czyli wariant drzew trie, w których zamiast liter do etykietowania krawędzi (w naszej implementacji: węzłów), stosujemy całe stringi. Takie skompresowane drzewo trie tworzy się przez usuwanie węzłów mających tylko jednego syna i modyfikację drzewa, tak aby skrócić ścieżkę poszukiwań.

Drzewa trie nie są jedynym sposobem na szybkie rozwiązanie opisanego na wstępie problemu, ale warto wiedzieć o ich istnieniu i umieć je zaimplementować. W szczególności dlatego, że w pewnych okolicznościach są wydajniejsze niż drzewa BST.

2009
04.24

W czwartek 23 kwietnia w hotelu Sheraton w Warszawie odbyło się spotkanie Steve’a Ballmera, CEO Microsoftu z członkami polskich społeczności IT. Byłem tam i z przyjemnością opiszę, jak wyglądało to spotkanie.

Steve przybył prawie punktualnie – nie licząc kwadransa akademickiego. Kiedy wchodził na salę miał na sobie czerwono-biały szalik z napisem “Polska”, który dostał od kogoś w Polsce i obiecał powiesić sobie w gabinecie.

Spotkanie zaczęło się od 15-minutowego przemówienia. Właściwie ta prelekcja bardziej przypominała show – Steve poruszał się na scenie oświetlony reflektorami – nie miał żadnych notatek ani pulpitu, nie prezentował żadnych slajdów.

W swoim wystąpieniu Steve poruszył kwestię kryzysu ekonomicznego i jego wpływu na techonologię. Zdaniem prezesa Microsoftu, właśnie teraz – podczas kryzysu – jest czas na inwestowanie w nowe rozwiązania i nowe technologie, które zaprocentują, gdy sytuacja gospodarcza poprawi się.

Ballmer powiedział także, że Microsoft  jest zdecydowany wydać w najbliższym czasie ok. 9 miliardów dolarów na badania i rozwój. Oznacza to 45000 etatów.

W jakich obszarach technologia będzie się rozwijać najbardziej prężnie? Wg CEO Microsoftu głównie w zakresie poprawiania interakcji między użytkownikiem a komputerem: zaawansowane rozpoznawanie mowy czy gestów. Steve wyraził także niezadowolenie ze sposobu w jaki, w dzisiejszych czasach działają wyszukiwarki (tu pozwolił sobie na drobne żarty z konkurencyjnego Google). Przytaczał statystyki, z których wynika, że około połowa poszukiwań konkretnych treści kończy się fiaskiem oraz że przeciętne zapytanie składa się z 2,2 słowa. Powiedział także:

Im więcej informacji dajesz wyszukiwarce, tym mniej zadowalających wyników otrzymujesz. Silnik nie radzi sobie z dużymi liczbami słów w zapytaniu. Gdy rozmawiasz z prawdziwym człowiekiem, jest zupełnie odwrotnie – im więcej informacji dostarczysz, tym lepszą odpowiedź dostaniesz.

Po tym krótkim wystąpieniu nadszedł czas na sesję pytań i odpowiedzi. Pytania dotyczyły najróżniejszych tematów, ale postaram się przytoczyć te najciekawsze:

Gdybyś miał opisać firmę Microsoft za pomocą jednego słowa, jakiego słowa byś użył?

Steve chwilę zastanawiał się, pytając z niedowierzaniem “one word? one word???”, kiedy nagle ktoś z sali krzyknął “awesome!”. Steve podchwycił i powiedział, że gdyby miał do dyspozycji dwa słowa powiedziałby:

Awesome, baby!

Poważna odpowiedź na pytanie brzmiała natomiast:

Popular innovation

Steve wyjaśnił, że Microsoft jest firmą, która dba o innowacje i wdraża nowe rozwiązania, ale jej celem jest poruszenie masowego odbiorcy – wpłynięcie na jak największą liczbę ludzi. Jako kontrprzykład podał firmę Apple – która wg niego jest innowacyjna, ale nie trafia do mas.

Inne pytanie, dotyczyło patentów. Steve wyraził pogląd, że patenty – jeśli reprezentują prawdziwą wartość – mają sens i popiera je. Skrytykował natomiast rozwiązania prawne zarówno w Stanach jak i w Unii, ponieważ według niego pozwalają na zgłaszanie bezsensownych patentów, za którymi nie kryje się żadna wartość.

Zapytany czy statystyki pokazujące, że większość specjalistów IT nie zmigruje do Windows 7 w pierwszym roku po jego wydaniu spowodują opóźnienie wydania nowego systemu, Steve powiedział, że jest to wręcz powód, dla którego Microsoft powinien przyspieszyć datę wydania siódemki. Wyraził także przekonanie, że ci sami specjaliści, którzy podejmą decyzję o pozostaniu przy XP czy Viście w firmach, w swoich domach na pewno bardzo szybko zainstalują nowy Windows.

Spotkanie szybko dobiegło końca, ale pozostawiło bardzo przyjemne wrażenie. Chyba głównie z powodu bezpośredniego, bezpretensjonalnego i bardzo energicznego stylu bycia Steve’a. Podczas całego spotkania utrzymywał żywy kontakt z publicznością, często żartował, swobodnie poruszał się po całej scenie, gestykulował. Bardzo dobrze się go słuchało.

Jedyną wadą spotkania było to, że trwało tylko jakieś 40 minut. No i Steve tym razem nie wykonał popisowego monkey dance… ;)

2009
01.28

Jeżeli mieszkasz w Bydgoszczy, to być może będziesz szczególnie zainteresowany poniższą informacją.

Od kilku miesięcy w Bydgoszczy działa lokalna grupa offline wspierana przez Microsoft - Bydgoska Grupa Developerów .NET. Jaki jest cel istnienia grupy?

W Internecie istnieje mnóstwo online’owych społeczności związanych z programowaniem w .NET – można choćby wymienić prężnie działający portal codeguru.pl. Nie wszystkie jednak wydarzenia mogą mieć miejsce w przestrzeni wirtualnej – na forach, blogach czy portalach. Programiści, którzy lubią swoją pracę mają potrzebę wymiany poglądów, uczenia się czegoś nowego, a chyba nie ma ku temu lepszej okazji niż spotkanie w realu. Dlatego właśnie w całej Polsce tworzone są lokalne grupy offline (w tej chwili jest ich ponad 20 i ciągle powstają następne).

BGD.NET organizuje spotkania (mniej więcej raz w miesiącu), na których prelegenci – profesjonalni programiści i specjaliści IT – prezentują wybrane zagadnienia związane z platformą .NET, programowaniem, bazami danych czy nowinkami w świecie programowania. Liczymy na to, że na spotkaniach po prezentacji wywiążą się ciekawe, owocne dyskusje.

Poza tym spotkania grup stanowią świetną okazję do poznania ludzi z innych firm w mieście, do zorientowania się czym zajmują się inni programiści czy jakie ciekawe projekty są rozwijane w regionie.

Zwykle grupy offline dysponują darmowymi pomocami technicznymi w formie elektronicznych prezentacji, podręczników czy przykładowych projektów. Dostępne są też książki (najczęściej wydane przez Microsoft Press), które można wypożyczyć.

Warto też dodać, że grupy zostały ostatnio objęte patronatem czasopisma NEXT.

Najbliższe spotkanie grupy bydgoskiej już 18 lutego. Prezentację poprowadzą Paweł Gliwiński i Marek Graczykowski – programiści zajmujący się IPTV. Tematyka będzie dotyczyć wzorców projektowych w języku C#. Może się wydawać, że o wzorcach projektowych powiedziano już wszystko, a jednak dla niektórych programistów ich właściwe używanie stanowi niemałą zagadkę. Prelegenci postarają się ją rozwiązać i zaprezentują praktyczne zastosowanie niektórych wzorców z przykładowymi scenariuszami użycia.

Zachęcam do założenia konta w portalu ms-groups.pl, a potem do rejestracji na 3. spotkanie BGD.NET.

A co jeśli bezkresne szczęście wynikające z mieszkania w Bydgoszczy nie jest Twoim udziałem? :> Sprawdź, czy w Twoim mieście działa grupa offline – portal ms-groups.pl.

2009
01.21

Większość programistów po zdobyciu pewnego doświadczenia odczuwa potrzebę oficjalnego poświadczenia swoich umiejętności. Taką możliwość dają certyfikaty – są honorowane na całym świecie i obiektywnie potwierdzają zdobytą wiedzę i praktyczne umiejętności.

Ponieważ na co dzień zajmuję się technologiami „okołodotnetowymi” – głównie pisaniem aplikacji webowych przy użyciu frameworka 2.0, postanowiłem podejść do egzaminu Microsoftu o łatwo zapamiętywalnym tytule 70-536 – TS: Microsoft .NET Framework – Application Development Foundation.

Tytuł egzaminu świetnie oddaje szeroki zakres obejmowany zagadnień. 70-536 sprawdza praktyczną i teoretyczną wiedzę dotycząca .NET 2.0. Obejmuje wszystkie dziedziny programowania poczynając od typów danych, operacje I/0 przez szyfrowanie danych i bezpieczeństwo kodu aż po globalizację i biblioteki do rysowania. Tematyka jest zatem rozległa, ale jednocześnie dość spójna.

Podczas przygotowywania się do egzaminu korzystałem głównie z książki z rodzaju self-paced (MCTS Self-Paced Training Kit (Exam 70-536): Microsoft® .NET Framework 2.0 Foundation). Rozdział po rozdziale omawia ona większość zagadnień dotyczących frameworka. Materiał jest dobrze podzielony pod względem logicznym, każdy rozdział zawiera kilka lekcji, które z kolei kończą się podsumowaniem i kilkoma pytaniami testowymi. Informacje jednak są podane w sposób niemal encyklopedyczny – nie jest to książka, w której autor „zaprzyjaźnia się” z czytelnikiem (natknąłem się jednak na jeden żarcik w rozdziale o serializacji: „Teleportation in science-fiction is a good example of serialization (though teleportation is not currently supported in .NET Framework)”. ;-)

Pomimo sporej objętości książka nie jest pełnym przewodnikiem po programowaniu w .NET, dlatego podczas przygotowań korzystałem także z biblioteki MSDN.

Poza tym książka niestety zawiera sporo błędów – przeglądając erratę trudno oprzeć się wrażeniu, że aż roi się od błędów. W rzeczywistości nie jest tak źle, a większość błędów można bez trudu wykryć samemu. Jest to jednak odrobinę drażniące.

Podczas przygotowań starałem się pisać testowe aplikacje opisane w labach po każdej lekcji. Myślę, że taki sposób interaktywnej nauki jest najefektywniejszy. W ten sposób szybciej zapamiętuje się kolejność wykonywania kroków dla pewnych specyficznych zadań (np. szyfrowanie pliku kluczem asymetrycznym czy wywołanie zapytania przez Windows API) oraz nazwy i sygnatury metod i konstruktorów

Do Training Kita dołączona jest płytka z przykładowymi testowymi pytaniami (w sumie ponad 300 pytań ze wszystkich kategorii). Na kilka dni przed egzaminem skupiłem się prawie wyłącznie na rozwiązywaniu zestawu testów. Przyzwyczaja to do formy pytań i uczy szukania odpowiedzi w jak najprostszy sposób (np. szybkie porównywanie odpowiedzi w celu wyeliminowania oczywistych błędnych). Z drugiej jednak strony po pewnym czasie większość pytań znałem na pamięć i w zasadzie nie czytałem ich treści. Jak można się domyśleć, nie miałem problemów, żeby zaliczyć każdy zestaw testowy.

Czas przygotowań upłynął szybko (słynne wrażenie jednego brakującego dnia) i nadszedł dzień egzaminu. Zdawałem w ośrodku szkoleniowym Softronic w Poznaniu. Miejsce niczym niewyróżniające się, nie zaskoczyło ani pozytywnie ani negatywnie. Chwilowym problemem była grupa młodych kobiet (złośliwi twierdzą, że grupy młodych kobiet zawsze stanowią problem dla programistów). Dziewczyny miały przerwę w szkoleniu i dość głośno wymieniały uwagi. Ten konkretny problem został rozwiązany, ale uważam, że w centrum szkoleniowym sale egzaminacyjne powinny być lepiej odseparowane od deskupiających dźwięków z zewnątrz.

Niestety, nie mogę napisać zbyt wiele na temat samego egzaminu, ponieważ każdy przystępujący podpisuję umowę NDA (Non-Disclosure Agreement). Mogę jedynie stwierdzić, że egzamin był trochę trudniejszy niż się spodziewałem, ale te godziny spędzone na przygotowaniach robią swoje i nawet jeśli nie byłem w stu procentach pewien odpowiedzi, to wyrobiona intuicja podpowiadała najbardziej prawdopodobne rozwiązania.
Intuicja mnie nie zawiodła – udało mi się zdać egzamin na 858 punktów (zaliczenie od 700) i uzyskałem tytuł MCP (Microsoft Certified Professional)!

Następnym krokiem będzie zdanie egzaminu 70-528: Web-based Client Development i, jeśli dobrze pójdzie, uzyskam tytuł MCTS (Microsoft Certified Technology Specialist).

Jeśli miałbym podsumować ten egzamin, to wydaje mi się, że najtrudniej jest postanowić, że się przystępuje do egzaminu. Bo kiedy ustali już się termin, to nie innego wyjścia – trzeba się nauczyć i zdać. Wszystkim przystępującym do egzaminu życzę powodzenia!

2009
01.20

Test

Halo, halo czy to działa?
Start.