niedziela, 30 grudnia 2012

Konwersja z bmp na jpeg w PowerShell

Kiedy masz dużo plików graficznych w bmp, warto jest zastanowić się nad zmiana formatu na jpeg. Pliki mają mniejszy rozmiar, a nie tracą tak bardzo na kompresji. Napisałem skrypt do konwertowania plików z jednego formatu na drugi. Wystarczy w kodzie wyselekcjonować pliki, które są bmp i w potoku wpisać nazwę funkcji:
gci -filter *.bmp | Convert-Jpeg

Poniżej cała implementacja funkcji zamieniająca bmp na jpeg:
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null

function Convert-Jpeg {
[CmdletBinding()]
Param(
     [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)] 
     [System.io.FileInfo] $filepath
)

BEGIN {
     Write-Host "Converting Bitmaps to Jpegs" 
     $fileConvertedCounter= 0
}

PROCESS{
        $filePathFullName = $filepath.FullName
        if( Test-Path $filePathFullName)
        {
            $image  = [System.Drawing.Image]::FromFile($filepath.FullName)
            $bitmap = new-object System.Drawing.Bitmap($image)
            $newFileName = ($filePathFullName -replace '([^.]).bmp','$1') + ".jpg"
            $bitmap.Save($newFileName, ([system.drawing.imaging.imageformat]::jpeg))
            $fileConvertedCounter++
            Write-Host "Converted $filePathFullName into $newFileName"
            $bitmap.Dispose()
            $image.Dispose()        
        }else {
            Write-Host "Path not found."
        }
}

END{
    Write-Host "It was $fileConvertedCounter converted files" 
}

}

Ugly strings

Mam super pomysł jak ograniczyć pisanie kodu z duża ilością rozrzuconych tekstów. Wystarczy ustawić kolor czcionki w tekst edytorze na string tak, aby od razu tekst rzucał się w oczy :)


Efekt takiej zmiany wygląda okropnie :)



Taka czcionka wymusza na użytkowniku tworzenie regionów ze stałymi stringami w jednym miejscu albo wydzielenie tego tekstu do osobnego zasobu (klasy albo resourca).


sobota, 29 grudnia 2012

Kopiowania kodu bez svn

Jeżeli masz kod źródłowy, który jest podpięty pod system kontroli wersji (svn) to ciężko jest skopiować tylko sam kod bez plików do kontroli wersji. Za pomocą PowerShell można to osiągnąć znacznie szybciej niż ręcznie wykonując kopiowanie .
function Copy-File-Exclude-Svn {
Param(
    [string] $sourceLocation=".",
    [string] $destinationClocation=".\No_SVN"
)
$files = gci -force  -exclude *.svn-base,".svn",".gitignore"  $sourceLocation  | ? {  $_.PSIsContainer -and $_.Length -lt 230 } | gci -recurse

$files | foreach {
        $fileEnding = $_.FullName.Replace($sourceLocation, "");
        $firstItem = $_.FullName;
        $secondItem = $destinationClocation  + $fileEnding ;
        Write-Host "Copy from $firstItem to $secondItem";
        Copy-Item $firstItem $secondItem;
    }
}


Wywołanie kopiowania plików wygląda następująco:
Copy-File-Exclude-Svn 
-sourceLocation "C:\Source\Project1" 
-destinationClocation ""C:\Source\Project1_without_svn"


niedziela, 16 grudnia 2012

Dokument w prezentacji

--Co się stanie jak połączysz dokument tekstowy ze slajdem w swojej prezentacji??
--Powstanie SlideUment :D
Taki suchar.

Zapewne zapytasz co to takiego. Pozwól, że przybliżę ci ten problem. Siedzisz na prezentacji w ostatnim rzędzie. Prowadzący zaczyna mówić ciekawie, więc jesteś zainteresowany prezentacją. Wszystko jest ok, aż w pewnym momencie prezenter przełącza na nowy slajd, a w twoich oczach pojawia się 'wielki' dokument mówiący o wszystkich najważniejszych rzeczach, ale kompletnie nieczytelnie dla ciebie. Mnie w takich momentach ogarnia frustracja i więcej nie chce uczestniczyć w takich prezentacji.

Poniżej przedstawiam 3 przykładowe slideumenty:


Słowo Slideument oznacza dokument przedstawiony w formie slidu w prezentacji (postać hybrydy). Pojęcie to zostało opisane przez Garr Reynolds na stronie Presentation Zen. Dokument i prezentacja mają inne cele, dlatego powinny one być inaczej przedstawione.

Nie jestem specjalistą od dekompozycji prezentacji, ale jeżeli mógłbym ci doradzić w uniknięciu slideumentu to po prostu przedstaw najważniejsze kwestie i zamieść odnośnik do dokumentu na końcu prezentacji. Oprócz tego możesz też rozdzielić cały slideument na klika lepiej przedstawionych slidów.


piątek, 14 grudnia 2012

Dobre filmy

Czy kiedykolwiek zdarzyło ci się, że oglądałeś film w kinie, na który polecił ci kolega lub trailer był fajny i po parunastu minutach (nie liczę czasu z reklamami przed filmem) okazało się, że jest to kompletny badziew. Ja wtedy siedzę spokojnie i mówię do sobie, że „już zapłaciłem i poświeciłem czas na dojazd do kino, to jeszcze tą godzinę wytrzymam”. Jeżeli miałeś taką sytuacje jak ja, to może ci pomóc mechanizmu zaimplementowany na portalu filmweb. Teraz oglądam te dobre filmy, wygenerowane z rekomendacji.



Ale jakie są dobre, a jakie słabe filmy? Każda osoba ma swój własny gust filmów. Mechanizmem do wyliczania scoringu filmu nazywa się Gustomierz. Korzysta z wielu zmiennych, takich jak gatunek, głosy innych użytkowników czy twórcy filmu. Model do wyliczenia najlepszych filmów działa na bazie algorytmu K-najbliższych sąsiadów.
I pamiętaj. Szanuj swój czas jaki poświęcasz na oglądanie filmów.

czwartek, 13 grudnia 2012

Achievements w Visual Studio

Gry, aby były bardziej atrakcyjniejsze dla gracza, wprowadziło się termin achievementy, czyli osiągnięcia. Za każdą wykonaną specjalną rzecz, odblokowuje się pewne osiągnięcie. Później taki gracz może pochwalić się takim osiągnięciem przed kolegami czy na Facebooku. Osiągnięcia mają już minecraft, WoW czy Team Fortress (razem ze statystyką osiągnięć ). Możesz też zdobywać osiągnięcia na XBox360. Mimo tego, że kilka lat temu każdy potępiał ten pomysł, dziś dla większości graczy stanowią nieodłączną część zabawy. Do tego stopnia, że niektórzy z nich katują godzinami tytuły, na które nigdy by nawet nie spojrzeli, a wszystko po to, aby być na wyższym poziomie w rankingu.

Takie osiągnięcia są też dla Visual Studio. Powstał specjalny plugin do VS. Tak jak w grach komputerowych, za wykonaną czynność dostajemy osiągnięcie. VS Achievements nie tylko bawi, ale również uczy. Wykonując pewne osiągnięcia, możesz się nauczyć pewnych narzędzi, technologii czy sposobów pisania kodu. Jest tez ranking użytkowników VS.



Achievementy w grach wprowadzono po to, aby w prosty sposób mogły wydłużyć spędzanie czasu wolnego przed grą i zwiększyły więzi między graczem a grą. To samo dzieje się z VS. Dodatek do VS2012 można pobrać tutaj.

Nie mam wielkich osiągnięć, ale raz na jakiś czas będę się starał, aby wykonać jakieś 'głupie' osiągnięcie. Poniżej jest moja statystyka.

środa, 12 grudnia 2012

Problem rozwiązany przez czas

Wszyscy mówią, aby 'nie odkładać rozwiązania problemu na później', 'to co masz zrobić jutro, spróbuj zrobić dzisiaj' itd. Ja sam nie jestem specialistą od prokrastynacji, ale mam swoją metodę na rozwiązywanie problemów, a dokładnie na ich odkładanie. Nie staraj się od razu rozwiązać jakiś problem, ale ustal czy ten problem jest nieszkodliwy i niepilny. Mogą być problemy, które są nieszkodliwe i pilne (np. spóźnienie się na zajęcia, rozmowa telefoniczna z trudnym klientem), ale również mogą być szkodliwe i niepilne (np. problem w relacjach ze znajomymi, odkładanie na emeryturę). Jeżeli twój problem jest nieszkodliwy i niepilny to ustal jego ramy czasowe. Ustal do kiedy chcesz rozwiązać ten problem. Pamiętaj o tym, że każde rozwiązanie potrzebuje trochę czasu na realizację. Zapisz sobie ten problem i po jakimś czasie spróbuj powrócić do niego. Takie odkładanie problemów może spowodować, że te problemy same się rozwiążą. Czasami nie warto zamęczać się rozważaniami nad rozwiązaniem problemu. Czasami jest tak, że dany problem zostanie rozwiązany przez kogoś innego. Może się zdarzyć, że ten problem zostanie zastąpiony szkodliwszym problemem, a wtedy ten wcześniejszy przestanie istnieć.

Klasyfikowanie problemów ze względu na szkodliwość i pilność ma dużo wspólnego z macierzą Eisenhowera. Macierz(matryca) Eisenhower'a (Eisenhower matrix) można też nazywać Systemem Eisenhower’a (Eisenhower system), Siatką Eisenhower’a (Eisenhower grid), Polami Eisenhower’a (Eisenhower boxes) lub Kwadrat Eisenhower’a (Eisenhower square). Najważniejszą cechą macierzy jest pomoc w ustalaniu priorytetów w działaniach. Każdy cel, zadanie i czynność można nanieść na odpowiednią ćwiartkę.



Interpretacja macierzy jest następująca:
Ćwiartka I: rzeczy ważne i również pilne, które powinniśmy zrealizować sami lub delegować dla innych ( w natłoku innych spraw). W tej ćwiartce to zazwyczaj sytuacje nagłe, trudne do przewidzenia, lub skutki zaniedbania i niewykonywania regularnej pracy oraz sytuacje kryzysowe.

Ćwiartka II: rzeczy ważne, ale niepilne, które możemy rozłożyć w czasie lub wykonywać regularnie. Czynności, które mogłyby się znaleźć w tej części to: rekreacja, szkolenia, ćwiczenia fizyczne, przygotowania do projektu, regularna praca nad sobą.

Ćwiartka III: rzeczy pilne, jednak mało ważne – powinniśmy wykonać lub zlecić w drugiej kolejności, zaraz po zadaniach z części A. Czynności z tego obszaru nie są istotne lecz wymagają jak najszybszego wykonania, możemy do nich zaliczyć np. uregulowanie rachunków, rozmowa telefoniczna lub wizytę u znajomych

Ćwiartka IV: rzeczy mało ważne i mało pilne – po prostu możesz te rzeczy olewać. Najlepszym miejscem dla czynności z tej części jest kosz. Nie są one dla nas istotne. Zazwyczaj to one są tzw. pożeraczami czasu. Przykłady tej czynności to bezcelowe patrzenie w TV podczas nudnego programu. Zadania z tej ćwiartki można bez problemu przesunąć na dalszy plan. Odkładanie problemu nieszkodliwego i niepilnego należy do 4 ćwiartki.

Oczywiście trudno jest nakreślić uniwersalny schemat postępowania dla wszystkich ponieważ, wszystko jest uzależnione od naszych prywatnych preferencji.

Jak przyspieszyć kodowanie cz.4 (testy sparametryzowane)

To, co mi się najbardziej podoba w NUnit to 'sparametryzowane testy'. Najlepiej na przykładzie będę mógł pokazać ci piękno tego rozwiązania. Dajmy na to, że mamy klasę z 'skomplikowaną' logiką.
public class Sum
{
 public int Value1 { get; set; }
 public int Value2 { get; set; }

 public int Result
 {
    get { return Value1 + Value2; }
 }
}
Klasa Sum w polu Result zwraca sumę pól Value1 i Value2. Użycie tej klasy wygląda następująco:
var sum =  new Sum { Value1 = value1, Value2 = value2 };
sum.Result;
Stwórzmy do niego test sprawdzający działanie przykładowych liczb, dodawanie elementu neutralnego (zero) oraz dodawanie liczby przeciwnej.
        [Test]
        public void It_should_return_sum_3()
        {
            int value1 = 1;
            int value2 = 2;
            var sum = new Sum { Value1 = value1, Value2 = value2 };
            Assert.AreEqual(3, sum.Result);
        }

        [Test]
        public void It_should_return_0_because_number_of_opposing()
        {
            int value1 = 1;
            int value2 = -1;
            var sum = new Sum { Value1 = value1, Value2 = value2 };
            Assert.AreEqual(0, sum.Result);
        }

        [Test]
        public void It_should_return_0_because_adding_neutral_nuber()
        {
            int value1 = 2;
            int value2 = 0;
            var sum = new Sum { Value1 = value1, Value2 = value2 };
            Assert.AreEqual(2, sum.Result);
        }
Mamy 3 metody, które testują 3 różne przypadki. Od razu widać, że kod się powtarza, więc chcemy zredukować powtarzający się kod w tych testach. Możemy zastosować atrybut TestCase. W tym atrybucie podajemy wartości obiektów z jakimi ma być wywołana metoda. Przykład jest poniżej:
        [TestCase(1, 2, 3)]
        [TestCase(2, 0, 2)]
        [TestCase(1, -1, 0, TestName = "Because number of opposing")]
        public void It_should_return_sum(int value1, int value2, int expected)
        {
            var sum = new Sum { Value1 = value1, Value2 = value2 };
            Assert.AreEqual(expected, sum.Result);
        }
Dokładnie to mi się najbardziej podoba w NUnit. Mamy przetestowane 3 różne przypadki użycia i napisało się tylko jedną metodę. W tej metodzie 2 pierwsze argumenty atrybutu są jako dane wejściowe naszej operacji, a 3 argument jest jako wynik tej operacji. Dla poszczególnych przypadków testowych (TestCase), można jeszcze określić nazwę testu, opis, jaki wyjątek ma być wyrzucony oraz jaka wartość ma być zwracana przez tą metody. Możemy wykorzystać ten ostatni parametr.

        [TestCase(1, 2, Result = 3)]
        [TestCase(1, -1, Result = 0, TestName = "It should return 0")]
        [TestCase(2, 0, Result = 2, TestName = "Adding zero")]
        public int It_should_return_sum2(int value1, int value2)
        {
            var sum = new Sum { Value1 = value1, Value2 = value2 };
            return sum.Result;
        }
Przekształcenie bardzo przypomina 'normalną' (bez aserci) metodę, ale z atrybutami. Mam diaboliczny pomysł. Parametr Result możemy dodać do normalnej metod statycznej. W taki sposób można mieć kod implementacyjny i testy w jednym projekcie - it's crazy.

    public class SumFactory
    {
        [TestCase(1, 2,  Result = 3)]
        [TestCase(1, -1, Result = 0, TestName = "It should return 0")]
        [TestCase(2, 0,  Result = 2, TestName = "Adding zero")]
        public int Sum(int value1, int value2)
        {
            return value1 + value2;
        }
    }
To jest pierwszy przykład jak możemy wykorzystać TestCase - drugi przykład jest dla TestFixture. Napiszmy generyczną klasę, która będzie dodać 2 zmienne:

    public class GenericSum<T>
    {
        public T Value1 { get; set; }
        public T Value2 { get; set; }

        public T SumValue
        {
            get { return (dynamic)Value1 + (dynamic)Value2; }
        }
    }
I chcielibyśmy przetestować dodawanie domyślnych wartości dla zmiennych typu int i double.
    [TestFixture(typeof(int))]
    [TestFixture(typeof(double))]
    public class GenericTestNUnit<T>
    {
        [Test]
        public void It_should_return_default_value()
        {
            var sum = new GenericSum<T> { Value1 = default(T), Value2 = default(T)};
            Assert.AreEqual(default(T), sum.SumValue);
        }
    }
Argument w atrybucie TestFixture odnosi się do typu T. Takie sparametryzowane testy mają zastosowanie w testowaniu klas dziedziczących.

    public abstract class AsBiOperation
    {
        public int Value1 { get; set; }
        public int Value2 { get; set; }
        public abstract int Result { get; }
    }
    public class SumOperation : AsBiOperation
    {
        public override int Result
        {
            get { return Value1 + Value2; }
        }
    }
    public class MultiplicationOperation : AsBiOperation
    {
        public override int Result
        {
            get { return Value1*Value2; }
        }
    }
Do stworzonej klasy abstrakcyjną i napiszemy test dla wszystkich klas dziedziczących po niej. Dodatkowo mamy możliwość wprowadzenia przykładowych wartości.

    [TestFixture(1,2,3,     TypeArgs = new[] { typeof(SumOperation)} )]
    [TestFixture(0, 0,0,    TypeArgs = new[] { typeof(SumOperation) })]
    [TestFixture(-1, 1, 0,  TypeArgs = new[] { typeof(SumOperation) })]

    [TestFixture(1, 0, 0,   TypeArgs = new[] { typeof(MultiplicationOperation) })]
    [TestFixture(1, 5, 5,   TypeArgs = new[] { typeof(MultiplicationOperation) })]
    [TestFixture(5, 5, 25,  TypeArgs = new[] { typeof(MultiplicationOperation) })]    
    public class AsBiOperationTests<T>
        where T:AsBiOperation, new()
    {
        T operation;
        int _value2;
        int _value1;
        int _result;

        public AsBiOperationTests(int value1, int value2, int result)
        {
            _result = result;
            _value1 = value1;
            _value2 = value2;
        }

        [SetUp]
        public void SetUp()
        {
            operation = new T();
        }


        [Test]
        public void It_should_calculate()
        {
            operation.Value1 = _value1;
            operation.Value2 = _value2;
            Assert.AreEqual(_result,operation.Result);
        }
        
    }

Testy sparametryzowane to fantastyczne rozwiązanie, aby mniej kodować, mieć większe pokrycie kodu i móc więcej przetestować przypadków. Bardzo żałuje, że w MSTest jeszcze nie ma takiego featura (chociaż nie bezpośrednio). Jednak mam nadzieję, że w bliskiej przyszłości coś się z tym zmieni. Oprócz NUnit'a sparametryzowane test są w MBUnit.

piątek, 7 grudnia 2012

Rozpakowanie gz w PowerShell

Ostatnio miałem problem z rozpakowaniem dużej ilości spakowanych plików, wiec musiałem napisać skrypt, który będzie mógł rozpakować wszystkie pliki do wyznaczonego katalogu. Z pomocą przyszedł mi Power Shell, a skrypt wyglądał następująco:
$files = gci -Filter *.zip 
$dirInto = (Get-Location).Path
foreach ($file in $files) {
Write-Host "Processing $file"
$shell_app=new-object -com shell.application
$fileFullName = $file.FullName

$zip_file = $shell_app.NameSpace($fileFullName)
$destinationFolder = $shell_app.NameSpace($dirInto) 
$destinationFolder.CopyHere($zip_file.Items())
}
Rozwiązanie znalazłem na blogu msdn.
Wszystko ok, ale jak chciałem rozpakować pliki gz to już miałem problem. Zabrałem się za przerobienie kodu. Nie chciałem, aby rozwiązanie było uzależnione od jakieś aplikacji, na przykład od PowerShell Community Extensions
Na samym początku napisałem kod w c#, który posłużył mi za podkładke,w którym to korzystam z GZipStream.
using (FileStream fsRead =
 new FileStream(filePath, 
               FileMode.Open, 
               FileAccess.Read,
               FileShare.Read))
            {
                string fileName = 
Path.GetFileNameWithoutExtension(filePath);

                using (FileStream outFile = File.Create(Path.Combine(dirname,fileName)))

                using (GZipStream zipStream= 
new GZipStream(fsRead, 
CompressionMode.Decompress))

                    zipStream.CopyTo(outFile); //.NET 4.0
            }

Później zacząłem przerabiać ten kod na skrypt w PS.
function Unzip-Gzip
{
    param
    (
        [String]$inFile,
        [String]$outDir
    )
 
    if (! (Test-Path $inFile))
    {
        "Input file $inFile does not exist."
        exit 1
    }
 
    if (! (Test-Path $outDir))
    {
        "Output direcotri $outDir does not exist."
        exit 1
    }
   

    $inputFile = New-Object System.IO.FileStream $inFile, 
([IO.FileMode]::Open), 
([IO.FileAccess]::Read), 
([IO.FileShare]::Read)


     try{
        $newFileName = 
[System.IO.Path]::GetFileNameWithoutExtension($inFile)

        $newFilePath = 
[System.IO.Path]::Combine($outDir, $newFileName)  

        $outputDir = 
[System.IO.File]::Create($newFilePath)

        try{
            $inputFileGzip = 
New-Object System.IO.Compression.GZipStream $inputFile, ([System.IO.Compression.CompressionMode]::Decompress)

            try{
                $inputFileGzip.CopyTo($outputDir)
            }finally{
                $inputFileGzip.Close()
            }
        }finally{
            $outputDir.Close()
        }
    }finally{
        $inputFile.Close()
    }
}
Niestety nie mogłem wywołać metody CopyTo, gdyż ta metoda pojawiła się dopiero w .NET 4.0 i nie występuje w PS 2.0. Zamiast tej metody stworzyłem funkcje, która mogła przekopiować Stream.
function Copy-Stream 
{
    param
    (
        [System.IO.Stream]$inputStream,
        [System.IO.Stream]$outputStream
    )

 $bufferSize = 1024
 $buffer = New-Object byte[] $bufferSize
 
 while($true)
 {
    $byteCount = $inputStream.Read($buffer, 0, $bufferSize) 
    if ($byteCount -le 0)
    {
        break;
    }
    $outputStream.Write($buffer,0, $byteCount)
 } 
}

Ostatecznie funkcja Unzip-Gzip wyglada tak:
function Unzip-Gzip
{
    param
    (
        [String]$inFile,
        [String]$outDir
    )
 
    if (! (Test-Path $inFile))
    {
        "Input file $inFile does not exist."
        exit 1
    }
 
    if (! (Test-Path $outDir))
    {
        "Output direcotri $outDir does not exist."
        exit 1
    }
   


    $inputFile = New-Object System.IO.FileStream $inFile,
 ([IO.FileMode]::Open), 
([IO.FileAccess]::Read), 
([IO.FileShare]::Read)

     try{
        $newFileName =
[System.IO.Path]::GetFileNameWithoutExtension($inFile)

        $newFilePath = 
[System.IO.Path]::Combine($outDir, $newFileName) 
 
        $outputDir = 
[System.IO.File]::Create($newFilePath)
        try{
            $inputFileGzip = 
New-Object System.IO.Compression.GZipStream $inputFile, ([System.IO.Compression.CompressionMode]::Decompress)

            try{
#$inputFileGzip.CopyTo($outputDir)
#Not in POWERSHELL 2.0
                Copy-Stream  $inputFileGzip $outputDir
            }finally{
                $inputFileGzip.Close()
            }
        }finally{
            $outputDir.Close()
        }
    }finally{
        $inputFile.Close()
    }
}
A skrypt do rozpakowania plików gz wygląda następująco:
$files = gci -Filter *.zip 
$dirInto = (Get-Location).Path
foreach ($file in $files) {
Write-Host "Processing $file"
$shell_app=new-object -com shell.application
$fileFullName = $file.FullName

Unzip-Gzip $fileFullName  $dirInto
}


Ryan Gosling też programuje

Ryan Gosling - kanadyjski aktor, gitarzysta, a w wolnych chwilach ... programuje


I kto mówił, że język IT nie może być romantyczny :)

Więcej ciekawych zdjęć pod tym blogiem.

Ewolucja lambda expression

Na początku był delegat.


Delegat jest odpowiednikiem wskaźnika do funkcji w C/C++. Jest silnie typowany, co oznacza, że delegat może wskazywać tylko na określony typ metody. Dajmy na to, że potrzebujemy sprawdzić czy pewna wartość jest większa czy mniejsza od zadanego progu. Stwórzmy delegat, który będzie miał 2 argumenty (wartość zmiennej oraz wartość progu) i będzie zwracał bool.
delegate bool ComparisonDelegate
(int value, int threshold);


public static bool IsGreaterThan
(int value, int threshold)
{
    return value > threshold;
}


public static bool IsLessThanOrEqualTo
(int value, int threshold)
{
    return value <= threshold;
}

Zastosowanie takiego delegata wygląda następująco:

ComparisonDelegate greaterThan = 
new ComparisonDelegate(IsGreaterThan);

ComparisonDelegate lessThanOrEqualTo =
new ComparisonDelegate( IsLessThanOrEqualTo);

bool is1GreaterThan3 = 
greaterThan(1, 3);

bool is5LessThanOrEqualTo4 = 
lessThanOrEqualTo(5, 4);

Takie rozwiązanie składała się z wielu linijek kodu oraz z paru oddzielnych części (deklaracja delegata, napisanie oddzielnych metod, inicjacja instancji delegatu, wywołanie delegata).Początki są ciężkie :)
Pierwszym uproszczeniem jest zastosowanie nazwanych metod. Już nie jest potrzebne wywołanie konstruktora z parametrem do metody, tylko wystarczyło podać nazwę metody.
ComparisonDelegate greaterThan = IsGreaterThan;
W .NET 2.0 przyszedł na świat koncept zwany metodami anonimowymi.
delegate bool ComparisonDelegate(int value, int threshold);


ComparisonDelegate greaterThan = delegate
(int value, int threshold)
{
return value > threshold;
};

bool is1GreaterThan3 = greaterThan(1, 3);

Dzięki anonimowym metodom można wprowadzić logikę biznesową do 'ciała'. Już nie musimy deklarować dodatkowych metod. Skoro teraz za każdym razem kiedy tworzymy instancje używamy słowa 'delegate' to można to zamienić na znak '=>'.
ComparisonDelegate greaterThan = 
delegate(int value, int threshold)
{
return value > threshold;
};
ComparisonDelegate greaterThan = 
(int value, int threshold)=>
{
return value > threshold;
};
Od momentu zamiany słowa delegate na znak '=>' mamy do czynienia z lambda expression. Lambda expression są rozwinięciem anonimowych metod. W tym kodzie znak '=>' oznacza, że pobiera 2 argumenty typu int, a zwraca bool. Jeszcze możemy usunąć klamry:
ComparisonDelegate greaterThan = 
(int value, int threshold)=> 
value > threshold;
I skoro wiemy, że ComparisonDelegate przyjmuje dwa argumentami typu int, to możemy tą zduplikowaną informację też usunąć.
ComparisonDelegate greaterThan = 
(value, threshold)=> 
value > threshold;
Po takich operacjach, składnia jest krótsza i czytelniejsza. Całość wygląda następująco:
delegate bool ComparisonDelegate(int value, int threshold);

ComparisonDelegate greaterThan =
(value, threshold)=> 
value > threshold;

bool is1GreaterThan3 = greaterThan(1, 3);
Wraz z .NET 3.5 (c#3.0) możemy użyć generycznego delegata o nazwie Funk. Dzięki temu nie będziemy musieli deklarować dodatkowego elementu. Deklaracja wygląda następująco:
delegate bool ComparisonDelegate(int value, int threshold);

delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
Zamieniamy ComparisonDelegate na Func:
Func<int, int, bool> greaterThan = 
(value, threshold)=> 
value > threshold;
Jak widać przejście z delegatów do lambda expressions jest całkiem ... szybkie:)

czwartek, 6 grudnia 2012

Any w listach

Czasami w kodzie widzę coś takiego:
 IEnumerable<int> numbers= new List<int> { 1,2,3,11,22,33};

 if (numbers.Count() > 0)
 { 
   //code... 
 }

Nic wielkiego jeżeli mamy listę do tyś. elementów, ale co się stanie, gdy lista składa się z dużo większej liczby elementów lub elementy są jako 'ciężkie obiekty'. Metoda Count() ma liniową złożoność obliczeniową. Poniższa tabela przedstawia ilość elementów i czas działania metody Count w ms. Oczywiście na innym komputerze czas działania metody będzie inny.

CountCount time
1000
1,0000
10,0000
100,0001
1,000,00013
10,000,000130
100,000,0001332
1,000,000,00013243


Zastanawiam, się dlaczego programiści nie używają metody Any(), która ma złożoność O(1). Metoda ta sprawdza czy jest przynajmniej jeden element w liście. A co jeżeli będziemy chcieli sprawdzić czy w liście jest przynajmniej n-elementów? Ja wpadłem na parę rozwiązań.

if (numbers.Count() >= indexOf)
{
//count
}

if (numbers
.Select((x, i) => new { x, i })
.Any(arg => arg.i >= indexOf))
{
//select&any
}


if (numbers
.Skip(indexOf)
.Any())
{
//skip&any
}

if (numbers
.ElementAtOrDefault(indexOf+ 1) != default(int))
 {
 //elementAt
}

if (numbers
.Take(hasIndexes)
.Count() == indexOf)
{
//take&count
}


Zmienna indexOf określa ile elementów chcielibyśmy, aby było w liście. Metoda ElementAtOrDefault mogła by spełniać warunek zadania tylko wtedy, gdyby lista nie zawierała domyślnej wartości elementu. Poniżej przedstawiam wyniki czasu działania dla tych sposobów. Czas jest liczony w ms.

IndexOfCountSelect&AnySkip&AnyElementAtTake&Count
1282652101
10277690000
100279160000
1,000286040000
10,000284230000
100,000286053112
1,000,0002854132121421
10,000,00028408321128137205
100,000,000285293222130213902065
1,000,000,0002858332342132681378920489

Wykres w skali liniowej i logarytmicznej:



Wniosek jest taki, że najlepiej przeskoczyć potrzebną liczbę elementów w liście i sprawdzić czy jest jakiś element (skip&any).

Gdyby lista byłaby IList<T> to można jeszcze zastosować indexOf lub właściwość Count:

if (numbers
.IndexOf(hasIndexes) != -1)
{

}

Jeżeli znasz inne sposoby to opisz je w komentarzu. Jestem ciekaw co można jeszcze zrobić.

Captured variables

Chciałbym napisać o czymś co się ostatnio dowiedziałem. Kompilator C# używa delegatów nie tylko po to, aby referować do metod, ale również aby przechowywać pewien kontekst - może przechowywać zmienne, aby były dostępne przez różne metody. Przypuśćmy, że mamy taką metodę:
public static bool IsGreaterOrEqualThan(
int value, 
int threshold)
{
 return value >= threshold;
}

Metoda pobiera dwa argumenty. Pierwszy zawiera wartość jaką chcemy sprawdzić, a drugi argument określa wartość progową. Zróbmy z tego predykat - metodę, która zwróci instancje delegata.
public class Pred
{
public static Predicate<int> IsGreaterOrEqualThan(
int threshold)
{
 return value => value >= threshold;
}
}

Wywołanie wygląda następująco:
Predicate greaterorEqualThan100 =
 IsGreaterOrEqualThan(100);

bool isValue1GreaterOrEqual   = greaterorEqualThan100 (1);
bool isValue999GreaterOrEqual = greaterorEqualThan100 (999);

Wartość graniczna (threshold) nie jest argumentem wbudowanej metody (inline method), ale samego predykatu. Kiedy tworzymy instancje predykatu to .NET tworzy dodatkową 'magię'. Kompilator generuje klasę, aby mógł przetrzymywać zmienne. Ta klasa będzie zawierała metodę oraz zmienną. Każde wywołanie metody będzie miało własną wartość zmiennej threshold. Jak pokazuje refleksja, wygenerowana klasa <>c__DisplayClass1 ma nazwę niedozwolona przez c#.
 public class Pred
  {
    public Pred()
    {
      base..ctor();
    }

    public static Predicate<int> 
IsGreaterOrEqualThan(int threshold)
    {
      Pred.<>c__DisplayClass1 cDisplayClass1 = 
new Pred.<>c__DisplayClass1();

      cDisplayClass1.threshold = threshold;

      return new Predicate<int>(
(object) cDisplayClass1, 
__methodptr(<IsGreaterOrEqualThan>b__0));

    }

    [CompilerGenerated]
    private sealed class <>c__DisplayClass1
    {
      public int threshold;

      public <>c__DisplayClass1()
      {
        base..ctor();
      }

      public bool <IsGreaterOrEqualThan>b__0(int value)
      {
        return value >= this.threshold;
      }
    }
  }


Tutaj należy poruszyć dwie ważne kwestie. Uchwycone zmienne (polskie tłumaczenie Captured Variables) mogą spowodować, że kod w pętlach zacznie inaczej działać niż spodziewaliśmy się na samym początku. Po drugie, lokalne zmienne typu ValueType nie zawsze są na stosie (stack) - kompilator kopiuje lokalną zmienną (threshold) do pola obiektu, który znajduje się na stercie (heap). Ta czynność ma bardzo duży wpływ na wydajność aplikacji.