Świąteczne małpki
25-12-2011

W ostatnim czasie w pracy spotkałem się z problemem który w uproszczeniu wygląda następująco:
Mamy listę obiektów typu Dział zawierających kolekcje obiektów typu Wydział zawierających kolekcję obiektów typu pracownik. Problemem do rozwiązania jest policzenie średniej Wieku wszystkich pracowników. Zakładamy dodatkowo że baza nie jest tuż za płotem tylko znajduje się za WebServicem do którego dokładanie metod jest z jakiś przyczyn problematyczne, więc nie możemy po prostu złożyć składnego selecta.
Problem wydaje się być banalny wystarczy zmontować coś takiego:
int ilosc = 0;
            int suma = 0;

            foreach (Dzial dzial in dzialy)
            {
                foreach (Wydzial wydzial in dzial.Wydzialy)
                {
                    foreach (Pracownik pracownik in wydzial.Pracownicy)
                    {
                        ilosc++;
                        suma += pracownik.Wiek;
                    }
                }
            }

            double wynik = ((float)suma) / ilosc;

Jest to jednak kod mało czytelny. A jeżeli operacja byłaby bardziej złożona niż średnia robi się jeszcze mniej ciekawie.
Rozwiązaniem jakie narzuca się bardziej doświadczonym programistom .Net jest użycie LINQ. Rozwiązanie to ma jednak wadę: nie chodzi na kolekcjach kolekcji tylko na jednowarstwowych kolekcjach. Można ten problem rozwiązać przez wyliczenie sobie pomocniczej kolekcji i na niej wykonania działania:
           List<Pracownik> pracownicy = new List<Pracownik>();

            foreach (Dzial dzial in dzialy)
            {
                foreach (Wydzial wydzial in dzial.Wydzialy)
                {
                    foreach (Pracownik pracownik in wydzial.Pracownicy)
                    {
                        pracownicy.Add(pracownik);
                    }
                }
            }

            double wynik = pracownicy.Average(p => p.Wiek);

Jest to rozwiązanie dobre jednak tworzenie i napełnianie listy w przypadku dużych kolekcji może okazać się zasobożerne.
Dlatego też postanowiłem zrobić świąteczny prezent sobie i innym programistom i stworzyć małpkę która za nas będzie biegała po drzewach i strukturach drzewo-podobnych. W ten sposób powstało rozszerzenie ExtractSubCollection biblioteki Szogun1987.Monkey. Po wykorzystaniu tej metody powyższy kod zostaje skrócony do 4 w miarę czytelnych linijek:
double result = 
                dzialy.ExtractSubCollection(d => d.Wydzialy)
                .ExtractSubCollection(w => w.Pracownicy)
                .Average(p => p.Wiek);


W bibliotece znajdują się dodatkowo rozszerzenia GetAllSubNodes, GetLeafs i GetRoot ułatwiające przeglądanie zwykłych (tj. składających się z węzłów spójnych pod względem typu) Drzew.
Najprostszą z nich jest GetRoot która jako parametr pobiera obiekt - węzeł drzewa i Funkcję zwracającą element nadrzędny węzła. Jako korzeń uznawany jest element dla którego powyższa funkcja zwróci null.
Kolejną metodą jest GetAllSubNodes pozwala ona na przeiterowanie po wszystkich węzłach znajdujących się poniżej węzła na którym wywoływane jest rozszerzenie (łącznie z nim samym). Jako drugi parametr przyjmowana jest funkcja zwracająca kolekcję elementów podrzędnych.
Ostatnią metodą w bibliotece jest GetLeafs pozwalająca przeiterować się po liściach drzewa z niego się wywodzących.
Dodatkowo w bibliotece znajduje się interfejs ITreeNode którego zaimplementowanie sprawia że do funkcji GetAllSubNodes, GetLeafs i GetRoot nie trzeba przekazywać funkcji wyciągających "najbliższych członków rodziny" danego elementu.

Dołożyłem starań aby biblioteka była napisana w sposób powodujący jak najniższe zużycie zasobów systemowych (pamięci i procesora). Podobnie jak w LINQ wszelkie funkcje i metody przekazywane do moich rozszerzeń są wywoływane w momencie iteracji.

Na koniec kwestie prawne, (Co by było wszystko na papierze znaczy się monitorze):
Zezwalam na nieodpłatne korzystanie ze skompilowanej wersji biblioteki. Biorę na siebie poprawki ewentualnie wykrytych błędów, przy czym nie naprawiam błędów wynikających ze współbieżnego dostępu do drzewa. Nie gwarantuję także wprowadzania ulepszeń.
Kodu źródłowego nie udostępnię w tym roku. Zakładam taką możliwość w pierwszym kwartale roku przyszłego (2012).

Biblioteki są do pobrania Tutaj

Tagi:

Programowanie C Sharp


Komentarze:

Tomek
30-12-2011

Proponuje zapoznac sie z metoda LINQ o nazwie SelectMany(). Uzywajac jej kod moze wygladac np. tak (przy niewielkiej ilosci danych):

var pracownicy = dzialy.SelectMany(d => d.Wydzialy).SelectMany(w => w.pracownicy).ToArray();

var iloscPracownikow = pracownicy.Count();
var sredniWiek = pracownicy.Average(p => p.Wiek);

Jezeli pominales ja celowo to chetnie bym sie dowiedzial jakie sa zalety Twojej metody w stosunku do tej z LINQ :)

th
30-12-2011

Dlaczego nie możesz zrobić selecta jedynie po tabeli Pracownicy? Struktura drzewiasta do pisanego problemu nie jest potrzebna, skoro liczysz wszystkim pracownikom

ttr
31-12-2011

Dokładnie th, od pierwszego listingu kodu zachodzę w głowę "Ale po co ?". Select z tabeli Pracownicy powinien w zupełności wystarczyć

td:)
31-12-2011

Wytłumaczenie dlaczego select z tabeli pracownicy nie jest możliwy znajduje się na początku wpisu, ale od początku pomyślałem o tym, co napisał Tomek - co jest złego w SelectMany()?:)

szogun1987
01-01-2012

Select many jest jak najbardziej poprawny po prostu jakoś do nie znalazłem. Na szczęście pozostają jeszcze 3 pozostałe metody

Ostatnie wpisy

Subskrybuj kanał RSS Świąteczne małpki
Problem zwrotnych referencji w WCF
Słów kilka o krotkach.
Wrażenia po wizycie na 4Developers
Komunikacja PHP i C Sharp
Słów kilka o chciwości
Upgrade bloga przy użyciu jQuery
Osobisty sukces na CodeGuru
Google web elements
Zastosowanie LambdaEqualityComparer

O sobie samym

Nazywam się Wojciech Gomoła i jestem programistą .Net w firmie Atena usługi informatyczne i finansowe S.A. Na stronie tej będę dzielił się swoimi doświadczeniami związanymi z programowaniem w różnych językach programowania. Ale nie tylko.
Moje CV

Ciekawe linki

CodeGuru
DotnetoManiak
bloghellix.pl - Blog kozaka :P
blog.bojkar.pl - bojkar's dev blog
Instytut Ludwika von Misesa
Sławomir Sobótka Holistycznie o inżynierii oprogramowania[/link
[link=http://krzysztofhabich.pl/]Ostatnich gryza psy - Blog Krzysztofa Habicha

Kontakt

e-mail:
szog...@interia.pl
szog...@gmail.com




Ciekawe książki

http://img.amazon.ca/images/I/31bmX1EXVZL._SL75_SS50_.jpgThinking in Java
pictures/sztukawojny.jpgSztuka wojny
pictures/bogatyojciec.jpgBogaty ojciec biedny ojciec

Dług publiczny




Kalendarz



Znaczniki

Browser side
C Sharp
Ekonomia
Java
Java ME
Konferencje
NHibernate
PHP
Programowanie
Recaptchia
Wzorce projektowe
Zaloguj Valid XHTML 1.0 Transitional