poniedziałek, 31 marca 2014

Konwersja bool w PS

Dla mnie konwersja obiektu do typu bool w PS jest ciężka. Wystarczy przejrzeć poniższe przykłady, żeby się przekonać, że zupełnie inaczej się rzutuje do typu boolenowskiego niż w innych językach programowania. Dla uproszczenia dodałem rezultaty poleceń w komentarzach:
[bool]0 #false
[bool]-1 #true

[bool]''  #false
[bool]' ' #true

[bool]"" #false
[bool]" " #true

[bool]"0" #true
[bool]"-1" #true
[bool]"1" #true

[bool]"$true" #true
[bool]"$false" #true

[bool]$false #false
[bool]$null #false
Albo taki o to przykład:
$persistent = “False”
[boolean]$persistent #true
Jak się zastosuje inne metody to wszystko jest dobrze:
[boolean][System.Convert]::ToBoolean( $persistent) #false
[bool]::Parse($persistent) #false
Oprócz logiki dwuwartościowej może istnieć logika trójwartościowa (niewiadoma). Do takiej konwersji już ciężko jest zastosować typ bool.

Dlatego też stworzyłem funkcję do konwersji string na bool:
$TrueValues = @("yes", "y", "1", "tak")
$FalseValues = @("no", "n", "0", "nie")

function ConvertTo-Bool
{
    param(
    [Parameter(Mandatory=$true)]
    $value,
    [switch]$nullable
    )

    [bool]$outBool = $false
    if (![bool]::TryParse($value, [ref] $outBool)) #[ref] for ref and out parameter
    {
        [bool] $containsInTrue = $TrueValues -contains $value;
        if ($nullable)
        {
            if ($containsInTrue)
            {
                return $true;
            }

            if ($FalseValues -contains $value)
            {
                return $false;
            }
            return $null;
        }
        return $containsInTrue;
    }
    return $outBool
}
A poniżej przykłady użycia funkcji do konwersji:
ConvertTo-Bool "false" #$false
ConvertTo-Bool "falsssse" #$false
ConvertTo-Bool "true" #$true

ConvertTo-Bool "false" -nullable #$false
$shoud_be_null = ConvertTo-Bool "falsssse" -nullable #$null

ConvertTo-Bool "true"-nullable #$true

ConvertTo-Bool "nie" #$false
ConvertTo-Bool "tak" #$true

ConvertTo-Bool "nie" -nullable #$false
ConvertTo-Bool "tak" -nullable #$true


Algorytm wydania reszty

Znów bawiłem się PS, decimalami i algorytmami. Dzisiaj zachłanny algorytm wydawania reszty z płatności.

function Solve-ChangeMaking 
{
    param(
        [ValidateNotNull()]
        [ValidateRange(0.01, 10000)]
        [decimal]$rest=.99,

        [ValidateNotNullorEmpty()]
        [ValidateScript({ 
        if($_ -lt 0.01 -OR $_ -gt 500)
        {
            throw "Not allowed nominations. Use nomination from 0.01 to  500"
        }
        return $true
        })]
        [array]$nominations =@(.99)
    )

    [array]$returnChange =@()
    [decimal]$remainedRest = $rest 
    $availableNominations = New-Object "System.Collections.Generic.List``1[decimal]"  
    #it should be 'list of deciaml' because problem with removeAt method
    $nominations | % { #adding nominations to list of decimals
        $availableNominations.Add($_)
    }

    $quantity = 0
    while($remainedRest -gt 0)
    {
        [decimal]$max = ($availableNominations | measure -Maximum).Maximum
        $index = $availableNominations.IndexOf($max);
        if($index -lt 0)
        {
            break
        }

        if($remainedRest -ge $max )
        {
            $remainedRest -= $max       
            $returnChange +=$max
            $quantity++
        }
        
        $availableNominations.RemoveAt($index)

        if($availableNominations.Count -eq 0)
        {
            break
        }
    }

    return  New-Object PSObject -Property @{ 
        Quantity  = $quantity; 
        'Remained Rest' =  $remainedRest;
        'Change to return'  = ($returnChange -join ' ');}
}

Do przetestowania algorytmu będziemy chcieli wydać resztę 97 groszy mając do dyspozycji 1, 2, 10, 20 i 50 groszy. Oczywiście zabraknie nam pieniędzy do wydania reszy:
Solve-ChangeMaking -rest .97 -nominations 0.10, 0.20, 0.50, 0.02, 0.01 

I wyniki:

Quantity : 5
Change to return : 0,5 0,2 0,1 0,02 0,01
Remained Rest : 0,14


Albo inny przykład z większą ilością gotówki:
$money = (20, 10, 5, 2, 1, .50, .20, .10, 0.05, .02, 0.01) *5 + 50 + 50
Solve-ChangeMaking -nominations $money -rest (200-0.99) 

A wyniki są następujące:

Quantity : 11
Change to return : 50 50 20 20 20 20 10 5 2 2 0,01
Remained Rest : 0,00

piątek, 28 marca 2014

Demotywacja w pracy

Gdy zastanawiamy się nad motywacją w pracy to pierwsze co nam przychodzi do głowy to pieniądze. Czy tak jest na prawdę??
Chciałbym Ci przedstawić prezentację pewnego doktora psychologii o motywacji.




Autor prezentacji mówi o tym co nas demotywuje do pracy. Jednym z najważniejszych demotywatorów jest powtarzanie tej samej czynności i nie uzyskiwanie rezultatów z tej działalności - podobnie jest z Syzyfową pracą. Jeżeli już uzyskamy wynik naszej pracy to ten rezultat jest niszczony.

Przypominają mi się słowa jakie wypowiedział A. Einstein: Definicja szaleństwa to robienie w kółko tego samego i spodziewanie się odmiennych rezultatów.


Po przeprowadzeniu paru eksperymentów, Pan Dan dochodzi do wniosku, że ignorowanie ludzkiego wysiłku i pracy jaką włożyło się w wykonanie danego zadania jest prawie tak samo złe (demotywujące) jak wyrzucanie rezultatów swojej pracy.


Inne filmiki Dan Ariely można znaleź na jego stronie.

poniedziałek, 24 marca 2014

Pobieranie machine.config z poziomu PS

Machine.config jest bardzo ważnym plikiem. Takie pliki konfiguracyjne jak app.config czy web.config 'dziedziczą' po machine.config. Aplikacja .NET odczytuje ustawienia jakie znajdują się w machine.config, a później z innych plików konfiguracyjnych. Informacje z machine.config są dostępne dla wszystkich aplikacji na komputerze i dlatego mogą zawierać takie informację jak connection string czy informacje o serwerez SMTP.

Jak wiemy PS też jest aplikacją, która korzysta z machine.config. Poniżej zamieściłem skrypt do odczytu informacji z tego pliku:
function Get-MachineConfig
{
    param(
    [switch] $location,
    [switch] $content,
    [switch] $xml,
    [switch] $connectionStrings)

    $retLocation = [System.Runtime.InteropServices.RuntimeEnvironment]::SystemConfigurationFile
    if($location)
    {
        $retLocation 
    }

    $retContent = gc $retLocation
    if($content)
    {
       $retContent 
    }

    $retXml = [xml]$retContent 
    if($xml)
    {
         $retXml
    }

    if($connectionStrings)
    {
       $retXml.configuration.connectionStrings.add
    }
}


Na uwagę zasługuje parametr, który zwraca ścieżkę do używanego pliku konfiguracyjnego:
Get-MachineConfig -location

Oraz parametr do pobierania informacji o dostępnych połączeniach do baz danych:
Get-MachineConfig -connectionStrings

niedziela, 23 marca 2014

Proste sposoby użycia PS akceleratorów

Ostatnio zająłem się akceleratorami w PS. Jeżeli programujesz w PS to przynajmniej z tysiąc razy skorzystałeś z tego udogodnienia. Stosujesz akceleratory jak kastujesz obiekt, tworzysz nowy obiekt albo przy wywoływaniu statycznych metod.
Poniższy kod przedstawi ci jakie masz akceleratory:
$accelerators = [psobject].Assembly.gettype("System.Management.Automation.TypeAccelerators")
$accelerators::get

Oczywiście klasę TypeAccelerators możesz zrobić jako 'accelerator':
#$accelerators::Add('accelerators', $accelerators)
[accelerators]::Get

Takie dodawanie może być przydatne kiedy chcemy deklarować generyczną kolekcje np. listę albo stos:
$stackType = [type]'System.Collections.Generic.Stack`1'
$accelerators::Add('stack', $stackType)
[stack[int]]$stack  = 1,2,3,4

Poniższy przykład jest dla kolejki z intami:
$QueueType = [type]'System.Collections.Generic.Queue`1'
$accelerators::Add('queue', $queueType)
[queue[int]]$queue  = 1,2,3,4

Po wywołaniu można sprawdzić jak te dwie kolekcje zwracają elementy:
$stack #4,3,2,1
$queue #1,2,3,4

Oczywiście można dodać inne klasy, z których mają ciekawe statyczne metody:
$pathType = [type]'System.IO.Path'
$accelerators::Add('path', $pathType)
[path]::GetTempFileName()

Lub tez można dodać klasę Assert, aby testować swój kod:
[System.Reflection.Assembly]::
LoadWithPartialName('Microsoft.VisualStudio.QualityTools.UnitTestFramework')

$assertType = [type]'Microsoft.VisualStudio.TestTools.UnitTesting.Assert'
$accelerators::Add('assert', $assertType)

Sprawdzenie asserci wygląda następująco:
$actualIntValue= 11
[assert]::AreEqual(12,$actualIntValue)

piątek, 21 marca 2014

Czas generowania zmiennej losowej w PS

Ostatnio sprawdzałem szybkość działania polecenia do generowania zmiennych losowych z przedziału (0,1).
Sprawdzimy wywołanie zmiennych losowych 1000 razy i zapisanie tych zmiennych do tablicy:

$count =1000
$num1= @()
$num2= @()


Poniżej kod do sprawdzenia obiektu z klasy random:
$objRandom = new-object random
$measure1 = Measure-Command -Expression {
            for ($i =0; $i -lt $count; $i++)
            {
              $num1 += $objRandom.NextDouble()
            }
}

I w przypadku polecenia Get-Random (alias random):
$measure2 = Measure-Command -Expression {
            for ($i =0; $i -lt $count; $i++)
            {
               $num2 += (random)/[Int32]::MaxValue 
            }
}


Wynik czasu działania jednego z kilku wywołań:

$measure1: 
TotalSeconds      : 0,3785933
TotalMilliseconds : 378,5933

$measure2:
TotalSeconds      : 56,7377659
TotalMilliseconds : 56737,7659
Wynik mnie nie zaskoczył. Spowolnienie może mieć kilka powodów:
- korzystanie z aliasu
- polecenia PS są wolniejsze niż wywołania obiektów .NET
- przy każdym wywołanieu polecenia radnom, tworzy się obiekt, a w pierwszym przypadku obiekt już jest zainicjalizowany
- dodatkowa operacja dzielenie liczby losowej przez maksymalną liczbę int

czwartek, 20 marca 2014

Wielkość folderów w PS

Zastanawiałeś się jak można sprawdzić wielkość folderów w podkatalogach? Poniżej jest kod w PS, który wyświetla wielkość folderów w podkatalogach.

function Show-SpaceUsage
{
    [CmdLetBinding()]
    param(
        [Parameter(Mandatory=$true, Position=0)]  
        [ValidateScript({Test-Path $_ })]
        [string]$path,

        [decimal]$parentSize=0,

        [ValidateNotNullOrEmpty()]
        [string]$format = "N2"
    )
    $size = (Get-ChildItem $path -Recurse | ?{$_.PSIsContainer -eq $false} | measure -property length -sum).sum
   
    $parentPercentage =1
    if($parentSize -ne 0)
    {
       $parentPercentage = $size / $parentSize  
    }

    $item = gi $path
    new-object PSObject | 
            Add-Member -PassThru -MemberType NoteProperty -Name  Path  -Value $item.FullName |
            Add-Member -PassThru -MemberType NoteProperty -Name  Name  -Value $item.Name |
            Add-Member -PassThru -MemberType NoteProperty -Name  GB  -Value ("{0:$format}" -f ($size / 1GB)) |
            Add-Member -PassThru -MemberType NoteProperty -Name  MB  -Value ("{0:$format}" -f ($size / 1MB)) |
            Add-Member -PassThru -MemberType NoteProperty -Name  KB  -Value ("{0:$format}" -f ($size / 1KB)) |
            Add-Member -PassThru -MemberType NoteProperty -Name  "Percentage of Parent" -Value ("{0:$format}" -f $parentPercentage)

    $dirs =  Get-ChildItem $path | ?{$_.PSIsContainer -eq $True} | sort Name
    $dirs | %{
        Show-SpaceUsage -path $_.FullName -parentSize  $size
        Write-verbose ""
    }
}

Wyniki są sformatowane do tabeli:
Show-SpaceUsage -path "." -Verbose | Format-Table

Oczywiście można wyniki wyeksportować do csv i tam już posortować po największych folderach:
Show-SpaceUsage -path "." | Export-Csv -path "result.csv" -Delimiter ';' -NoTypeInformation -Encoding ASCII 

niedziela, 16 marca 2014

Sprawdzanie wolnej przestrzeni dyskowej w PS

Czy znasz może sposób na pobranie informacji o wolnych miejscach na dysku za pomocą PS?? Jedyna możliwość na uzyskanie tej informacji jest skorzystanie z WMI. Poniżej jest skrypt:
function Get-FreeSpace(
[string]$drive,
[string]$computer="localhost")
{
 ([wmi]"\\$computer\root\cimv2:Win32_logicalDisk.DeviceID='$drive'").FreeSpace
}
I możemy wykonać:
$freeSize = Get-DiskSpace -drive "E:"
Do tego wyniku możemy zastosować dzielenie przez jednostki przestrzeni pamięci:
PS>$freeSize
42215112704

PS>$freeSize / 1MB
40259,46875

PS>$freeSize / 1GB
39,3158874511719

Jeżeli byś znalazł inny sposób niż użycie WMI to daj znać

sobota, 15 marca 2014

WhoAmI - pierwsze polecenie na nowym komputerze

Jednym z pierwszych poleceń PowerShell wywołanych na Twoim nowym komputerze powinno być polecenie WhoAmI:
PS>WhoAmI 
<computer_name>\<user_name>
Można uzyskać listę wszystkich grup do których należysz (do tego możesz wyeksportować do csv'ki):
WhoAmI /GROUPS /FO CSV /NH
Oraz możesz uzyskać dodatkową listę uprawnień:
PS>WhoAmI /PRIV

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                           State   
============================= ===================================== ========
SeShutdownPrivilege           Zamknij system                        Disabled
SeChangeNotifyPrivilege       Obejd« sprawdzanie przy przechodzeniu Enabled 
SeUndockPrivilege             Usuä komputer ze stacji dokujĄcej     Disabled
SeIncreaseWorkingSetPrivilege Zwi©ksz zestaw roboczy procesu        Disabled
SeTimeZonePrivilege           Zmieä stref© czasowĄ                  Disabled

Wszystko w porządku pod warunkiem, że posiadasz system nowszy niż Windows XP. Jeżeli jednak nie posiadasz takiego systemu to będziesz musiał sam napisać taki program/skrypt. Na przykład w PowerShellu będzie wyglądał tak:
function WhoAmIPS
{
 [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
}

Aby uzyskać listę wszystkich grup do których należysz to powinieneś wywołać poniższą funkcję:
function WhatGroupAmIIn
{
    $user = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $nt = "System.Security.Principal.NTAccount" -as [type]
    $user.Groups | 
    ForEach-Object { $_.translate($NT)}
}

piątek, 14 marca 2014

Problem Józefa Flawiusza w PowerShell cz.2

Już wcześniej pisałem o problemie Józefa Flawiusz. W tamtych czasach nie miałem rozwiązania przedstawiającego wszystkie iteracje tylko rozwiązanie zwracało ostatni element. Napisałem od nowa algorytm do wyświetlenia wszystkich elementów.
Poniżej jest funkcja iteracyjna do wyświetlenia kolejności elementów:
function Iterate-JosephusFlavius(
        [ValidateRange(1,1000)]
        [int]$killEvery = 3,
        [ValidateRange(1,1000)]        
        [int]$soldiers = 41
      )
{
    [bool[]]$bools = @($false)*$soldiers
    $indexOfKilledSoldiers=@()
    $soldiersToKill = $soldiers
    $rest = 0
    $setps=0
    while($soldiersToKill  -gt 0)
    {
        for($i=$rest; $i -lt $bools.Length;$i++)
        {
            if($bools[$i] -eq $false)
            {
                $setps++
                if($setps -eq $killEvery)
                {
                    $bools[$i] = $true
                    $indexOfKilledSoldiers += $i
                    $soldiersToKill--
                    $setps=0
                }
            }
        }
        $rest = $i -$bools.Length
    }
    return $indexOfKilledSoldiers
}
A tutaj jest funkcja rekurencyjna. Z tą funkcją miałem więcej problemów. Niestety argumenty funkcji są inne dla iteracji jak i dla rekurencji. Jak będę miał chwilkę czasu to poprawię tą różnicę.
function Recursive-JosephusFlavius(
        [ValidateRange(1,1000)]
        [int]$killEvery = 3,
        [int[]]$soldiers,
        [int[]]$killedSoldiers=@(),
        [int]$prevRest = 0
      )
{
    if($soldiers.Length -eq 0 )
    {
        return $killedSoldiers
    }
    $aliveSoldiers=@()
    
    if($soldiers.Length - $killEvery -lt 0)
    {
        $soldierToKill = (($killEvery + $prevRest) -1) % $soldiers.Length 
        for($i=0; $i -lt $soldiers.Length; $i++)
        {
            if($soldierToKill -eq $i)
            {
                $killedSoldiers += $soldiers[$i]
            }else
            {
                $aliveSoldiers += $soldiers[$i] 
            }
        }  
    }
    else
    {
        for($i=0; $i -lt $soldiers.Length; $i++)
        {
            if(($i+1)  % $killEvery -ne 0)
            {
                $aliveSoldiers +=   $soldiers[($i-$prevRest)]  
            }else
            {
                $killedSoldiers +=  $soldiers[($i-$prevRest)]
            }
        }
        $soldierToKill = $soldiers.Length % $killEvery
    }
   return  Recursive-JosephusFlavius -soldiers $aliveSoldiers -killEvery $killEvery -killedSoldiers $killedSoldiers  -prevRest $soldierToKill 
}

Wywołanie tych funkcji oraz sprawdzenie działania wygląda następująco:
$soldiers = 0..40 #41 soldiers
$killEvery = 3

$res1= Recursive-JosephusFlavius -soldiers $soldiers -killEvery $killEvery
$res2 = Iterate-JosephusFlavius  -soldiers ($soldiers.Length) -killEvery $killEvery

for($i=0; $i -lt [math]::Max($res1.Length,$res2.Length); $i++)
{
    if($res1[$i] -ne  $res2[$i])
    {
        throw "not the same"
    }
    else
    {
        Write-Host $res1[$i]
    }
}

środa, 12 marca 2014

Pobieranie z GAC za pomocą PowerShell

Tak się zastanawiam, dlaczego PowerShell nie ma możliwości pobrania danych z GAC. PS wymaga zainstalowanego .NET, a najnowsze systemy z automatu mają ten framwork zainstalowany. Niestety jesteśmy zmuszeni do używania GacUtil.exe (Global Assembly Cache Tool). Napisałem skrypt do zwracania elementów z GACy.

function Get-GAC
{
    [CmdletBinding()]
    param()

& 'C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\gacutil.exe' /L |
? { $_ -match '^  ([\w\.]+,.*)$' } |
    % { 
        $fullName = ($Matches[1])
        try
        {
           $assembly = [System.Reflection.Assembly]::Load($fullName)
           $assembyName = $assembly.GetName()

           new-object PSObject -prop @{ 
                FullName = $assembly.FullName; 
                GAC = $assembly.GlobalAssemblyCache;
                Version = $assembly.ImageRuntimeVersion;
                FullyTrusted = $assembly.IsFullyTrusted;
                Location = $assembly.Location;
                Name = $assembyName.name;
                ReferencedAssemblie = $assembly.GetReferencedAssemblies();
                Token = ($assembyName.GetPublicKeyToken() |  foreach {$_.ToString("X2")}) -join '';
                HashAlgorithm = $assembyName.HashAlgorithm;
                CultureInfo = $assembyName.CultureInfo;
           }
        }
        catch [system.exception]
        {
            Write-Verbose $_
        }
    }| sort Name -unique 
}

Przykład użycia jest poniżej. Dodatkowo dodajemy parametr verbose, gdyż nie wszystkie biblioteczki są załadowywane. Jeszcze troszkę będę musiał przy tym posiedzieć i sprawdzić dlaczego nie wszystko zostaje załadowane. Może ty masz jakiś pomysł??
( Get-GAC -Verbose ) | out-gridview

poniedziałek, 10 marca 2014

Szybka implementacja WhatIf i Confirm w PS

Kiedyś nie wiedziałem o parametrach SupportsShouldProcess i ConfirmImpact. Z powodu tej niewiedzy ręcznie implementowałem WhatIf i Confirm za pomoca [switch] w parametrze.

Ten poniższy kod jest bardziej dla mnie niż dla kogoś innego, ponieważ teraz prawie zawsze staram się używać tych dwóch parametrów, a zawsze zapominam sobie o tych nazwach :)

function Get-CmdletJson
{
     [cmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')] 
     param()
    
    if($PSCmdlet.ShouldProcess(' $PSCmdlet'))
    {
        $PSCmdlet | ConvertTo-Json
    }
}

Kiedy wywoła się polecenie z parametrem WhatIF to wyświetli się poniższy komunikat:
Get-CmdletJson -WhatIf
#What if: Performing the operation "Get-CmdletJson" on target " $PSCmdlet".

W przypadku Conform to potwierdzenie jest uzależnione od hosta
Get-CmdletJson -Confirm

Poniższy rysunek przedstawia 2 różne host. Małe okienko potwierdzenia należy do PowerShell ISE, a duże okienko tekstowe należy do PowerShell ConsoleHost:



piątek, 7 marca 2014

MergeSort i Assert w PowerShell

Chciałem napisać prostą asercje i poćwiczyć sortowanie, więc napisałem script, z którym podzielę się z tobą :)
Na samym początku będziemy potrzebować funkcje, która będzie łączyła lewą i prawą stronę tablicy. Te części są posortowane.
function Merge
{
    param([array]$A,
    [array]$temp=@(),
    [int]$low=0,
    [int]$high=$($A.Length-1),

    [int]$middle = 
$([math]::Floor($low + ($high - $low)/2))
    )
    
    $i = $low
    $j = $middle +1
    
    Assert-Command {IsSorted ($A[$i..$middle]) }
    Assert-Command {IsSorted ($A[$j..$high]) }

    $temp = $A
    $swapA = @(0)* $A.Length
    for($k = $low; $k -le $high; $k++)
    {
        if( $i -gt $middle)
        {
            $swapA[$k] = $temp[$j]
            $j++        
        }
        elseif ($j -gt $high)
        {
            $swapA[$k] = $temp[$i]
            $i++
        }
        elseif( $temp[$j] -gt $temp[$i] )
        {
            $swapA[$k] = $temp[$i]
            $i++
        }
        else
        {
            $swapA[$k] = $temp[$j]
            $j++
        }
    }
    $sortedA =$swapA[$low..$high]

    Assert-Command {IsSorted $sortedA}
    return $sortedA
}
Napiszemy prostą funkcję do sprawdzenia czy elementy są posortowane:
function IsSorted([array]$arr)
{

    for($i=0; $i -lt $arr.Length-1; $i++)
    {
        if($arr[$i] -gt $arr[$i+1])
        {
            return $false
        }
    }
    return $true
}

Do funkcji scalania potrzebna jest funkcja asercji:
function Assert-Command
{
    param(
    [Parameter(Position=0,Mandatory=$true)]
    [ScriptBlock]$condition,

    [Parameter(Position=1)]
    [string]$message
    )
    $invoke = $MyInvocation

    try{
        $ErrorActionPreference = "STOP"
        $result = &$condition
    }catch{
        $result=$false
        $exceptionType = $_.Exception.GetType().FullName
    }


    if(-not($result))
    {
        $errMess = "Assert failed in line: $($invoke.Line) `n"
        $errMess += "Message: $message `n"
        $errMess += "Exception type: $exceptionType `n"
        $errMess += "Used parameters: $($invoke.BoundParameters.condition) `n"
        throw $errMess
    }
}
I na końcu potrzebujemy funkcji sortując:
function MergeSort-Object
{
    param([array]$A,
    [array]$temp=@(),
    [int]$low=0,
    [int]$high=$($A.Length-1),

    [int]$middle = $([math]::Floor($low + ($high - $low)/2))
    )
    
    if($low -ge $high)
    {
        return $A[$low]
    }

    [array]$sortedPart1 = MergeSort-Object -A $A -low $low -high $middle
    $A = Fill-Array -table $A -with $sortedPart1 -offSet $low

    [array]$sortedPart2  = MergeSort-Object -A $A -low ($middle+1) -high $high
    $A = Fill-Array -table $A -with $sortedPart2 -offSet ($middle+1)

    [array]$sorted = Merge -A $A -low $low -middle $middle -high $high
    return $sorted 
}
Niestety będziemy musieli użyć pomocniczej funkcji do wypełnienia tablicy już posortowanej części. Są to uroki przekazywania parametrów funkcji przez wartość, a nie przez referencje.
function Fill-Array
{
    param([array]$table,
    [int]$offSet=0,
    [array]$with
    )

    [array]$newTable = $table
    
    for($i =0; $i -lt $with.Length; $i++)
    {
        
        $newTable[$offSet + $i] = $with[$i]
    }
    return $newTable
}
I przykładowe sortowanie tablicy wygląda następująco:
 MergeSort-Object -A 1,3,7,99,5,3,32,5,11,91 

Kolejność wykonywania działań w PS


Już wcześniej pisałem o problemach kolejności wykonywania działań w PS, ale tym razem chciałem rozszerzyć ten przykład o kolejne przypadki:

Write-host " 2 + 3 * 4             is  $(2 + 3 * 4 )"
Write-host " `"2`" + 3 * 4         is  $("2" + 3 * 4     )"
Write-host " 2 + `"3`" * 4         is  $(2 + "3" * 4     )"
Write-host " 2 + 3 * `"4`"         is  $(2 + 3 * "4"     )"
Write-host " `"2`" + `"3`" * 4     is  $("2" + "3" * 4   )"
Write-host " `"2`" + 3 * `"4`"     is  $("2" + 3 * "4"   )"
Write-host " 2 + `"3`" * `"4`"     is  $(2 + "3" * "4"   )"
Write-host " `"2`" + `"3`" * `"4`" is  $("2" + "3" * "4" )"

Wynik takiego wywołania są następujący:

2 + 3 * 4 is 14
"2" + 3 * 4 is 212
2 + "3" * 4 is 3335
2 + 3 * "4" is 14
"2" + "3" * 4 is 23333
"2" + 3 * "4" is 212
2 + "3" * "4" is 3335
"2" + "3" * "4" is 23333

Zawsze sprawdzaj jaki typ danych masz po lewej stronie działania!!

wtorek, 4 marca 2014

Listy, słowniki, tuple i domyślne wartości w Power Shell

Tworzenie generyków w PS jest tak ciężkie i nie przyjemne w pisaniu, że ludzie nie używają ich. Postanowiłem coś z tym zrobić i napisałem funkcje, które tworzą obiekty generyczne:
function New-Generic
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string]$type,

    [Parameter(Mandatory=$true, Position=1)] 
    [ValidateNotNullOrEmpty()]
    [string[]]$typeParameter,

    [Parameter(Mandatory=$false, Position=3)] 
    [array]$constructorParameters=$null
    )

    $genericType = [type]($type + ‘`’ + $typeParameter.Count)
    [type[]] $tParameters = $typeParameter
    $closedType = $genericType.MakeGenericType($tParameters)

    Write-Verbose "Creating object $closedType with parameters $constructorParameters" 
    return ,([Activator]::CreateInstance($closedType, [Object[]]$constructorParameters))
}
Najwięcej czasu w napisaniu tej funkcji zajęło mi dodanie przecinka po słowie return. Ponad jeden dzień spędziłem w szukaniu dlaczego kastuje mi do zwyczajnego obiektu. Ten przecinek robi jakąś magię :)

Powyższa funkcja tworzy obiekt, który jest generyczny. Postanowiłem owrappować tą funkcję z najczęściej używanymi kolekcjami generycznymi:
function New-List
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string] $typeParameter 
    )
   return ,( New-Generic -type 'System.Collections.Generic.List' -typeParameter $typeParameter )
}


function New-LinkedList
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string] $typeParameter 
    )
   return ,( New-Generic -type 'System.Collections.Generic.LinkedList' -typeParameter $typeParameter )
}

function New-Queue
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string] $typeParameter 
    )
   return ,( New-Generic -type 'System.Collections.Generic.Queue' -typeParameter $typeParameter )
}

function New-SortedSet
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string] $typeParameter 
    )
   return ,( New-Generic -type 'System.Collections.Generic.SortedSet' -typeParameter $typeParameter )
}

function New-Stack
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string] $typeParameter 
    )

   return ,( New-Generic -type 'System.Collections.Generic.Stack' -typeParameter $typeParameter )
}

function New-HashSet
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string] $typeParameter 
    )

   return ,( New-Generic -type 'System.Collections.Generic.HashSet' -typeParameter $typeParameter )
}

function New-Dictionary
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string]$keyType,

    [Parameter(Mandatory=$true, Position=1)] 
    [ValidateNotNullOrEmpty()]
    [string]$valueType
    )
    return ,(New-Generic -type 'System.Collections.Generic.Dictionary' -typeParameter $keyType,$valueType)
}

function New-SortedDictionary
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string]$keyType,

    [Parameter(Mandatory=$true, Position=1)] 
    [ValidateNotNullOrEmpty()]
    [string]$valueType
    )
    return ,(New-Generic -type 'System.Collections.Generic.SortedDictionary' -typeParameter $keyType,$valueType)
}

function New-SortedList
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string]$keyType,

    [Parameter(Mandatory=$true, Position=1)] 
    [ValidateNotNullOrEmpty()]
    [string]$valueType
    )
    return ,(New-Generic -type 'System.Collections.Generic.SortedList' -typeParameter $keyType,$valueType)
}

function New-KeyValuePair
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string]$keyType,

    [Parameter(Mandatory=$true, Position=1)] 
    [ValidateNotNullOrEmpty()]
    [string]$valueType
    )
    return ,(New-Generic -type 'System.Collections.Generic.KeyValuePair' -typeParameter $keyType,$valueType)
}
Przykład użycia dla listy jest poniżej. Można zauważyć rzucenie wyjątku kiedy będziemy chcieli dodać obiekt do listy, który nie może być kastowany do int:
$list = New-List "int" -Verbose
$list.GetType() 
$list.Add(1)
$list.Add('2')
$list.Add("three") #throw exception
Również można zainicjalizować stos doubli:
$stack= New-Stack -typeParameter 'double'
$stack.getType()
$stack.Push(1)
$stack.Push(20000)
$stack.Push(-1.123)
$stack.Pop()
A tworzenie słownika wygląda następująco:
$dic = New-Dictionary -keyType 'string' -valueType 'int'
$dic.Add("one",1)
$dic.Add("two",2)
Zanim stworzy się funkcję dla tupli to będzie potrzebna funkcja zwracająca domyślne wartości:
function Get-DefaultValue
{
    param(
    [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
    [type]$type
    )
    process{

        if($type.IsValueType)
        {
            return [Activator]::CreateInstance($type)
        }
        return $null
    }
}

Wynik dla boola:
Get-DefaultValue 'bool' #False
A funkcja do tworzenia tupli jest następująca:
function New-Tuple
{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory=$true, Position=0)] 
    [ValidateNotNullOrEmpty()]
    [string[]] $typeParameter ,

    [Parameter(Mandatory=$false, Position=1)] 
    [array]$constructorParameters
    )

    if(-not($constructorParameters))
    {
        $constructorParameters = $typeParameter |Get-DefaultValue

    }
   return ,( New-Generic -type 'System.Tuple' -typeParameter $typeParameter -constructorParameters $constructorParameters )
}
Oczywiście można stworzyć tuple dla dowolnej (o ile pozwoli konstruktor) ilości parametrów
$tuple3= new-Tuple -typeParameter 'int', 'object', 'string'

$tuple3.GetType();
$tuple3.Item1

$tuple3= new-Tuple -typeParameter 'int', 'bool', 'string' -constructorParameters 1,$true, "SuperString"
$tuple3.Item2
$tuple3.Item3


poniedziałek, 3 marca 2014

Counting sort w PowerShell

Postanowiłem zaimplementować algorytm sortowania przez zliczanie w PowerShellu. Jest to najszybszy algorytm sortowania, ale jest tylko dla licz naturalnych. Jakiś czas temu miałem projekt, w którym porównywałem szybkość działania algorytmów, ale z powodu braku czasu i zaniedbania został niestety usunięty :/ Może kiedyś utworze nowy projekt na GitHubie.


function CountingSort-Object
{
    param(
    [int[]]$in
    )
    $max = ($in | measure -Maximum).Maximum +1
    $count=@(0) * $max #initialization

    $in | %{        
        $count[$_]++   
    }

    $out=@()
    $count | % {$i=0 }{ 
        if($_ -gt 0)
        {
            $out+= (@($i) * $_)  
        }
        $i++
    }
    return $out
}

Przykład użycia jest poniżej
CountingSort-Object 11,2,5,4,5,11,0,19,0
CountingSort-Object (1..100| random -Count 10)


Tak się zastanawiam czy można dodać tablicę $out2, która będzie zawierała ilość liczb ujemnych? Wtedy możemy rozszerzyć dziedzinę z liczb naturalnych do licz całkowitych.