PowerShell Sayma Sorununu Çözme


Birkaç hafta önce bir Iron Scripter PowerShell komut dosyası oluşturma yarışması yayınlandı. Tüm bu zorluklarda olduğu gibi süreç sonuçtan daha önemlidir. Bir çözümü nasıl bulacağınız, bir PowerShell uzmanı olarak nasıl gelişeceğinizdir. Birkaç kişi zaten çalışmalarını paylaştı. Bugün benimkini paylaşmayı düşündüm.

Acemi

Başlangıç seviyesindeki zorluk, 1 ile 100 arasındaki çift sayıların toplamını bulmaktı. En az 3 farklı teknik bulabilmeniz gerekir.

Döngü için

İlk çözümüm For döngüsü kullanmak.

$t = 0
for ($i = 2; $i -le 100; $i += 2) {
    $t += $i
}
$t

$t değişkeni 0'a ayarlanmıştır. Bu toplam değer olacaktır. For döngüsünün sözdizimi şöyle der: "$i ve 2 değeriyle başlayın ve $i 100'den küçük veya ona eşitken döngüye devam edin. Her döngü yaptığınızda $i değerini 2 artırın." += operatörü $i=$i+2 demenin kısayol yoludur. Bu, 100'e kadar (100 dahil) tüm çift sayıları elde etmelidir. -lt operatörünü değil, -le operatörünü kullanıyorum. Döngü boyunca her seferinde, { } içindeki kodda, $t'yi $i'nin değeri kadar arttırıyorum. Sonunda $t için 2550 değerini elde ediyorum.

Modulo Operatörü

Bir sonraki teknik Modulo operatörünü - % kullanmaktır.

1..100 | Where-Object {-Not($_%2)} | Measure-Object -sum

Ardışık düzen ifadesinin ilk kısmı, 1 ile 100 arasındaki tüm sayıları elde etmek için Range operatörünü kullanmaktır. Her sayı, Modulo operatörünü kullanan Where-Object'e aktarılır. Her sayıyı 2'ye bölüyorum. Eğer kalan varsa örneğin 3/2 1,5 sonuç 1 oluyor. Aksi takdirde operatör 0 sonucunu üretiyor. 1 ve 0 da boole değerleri olarak yorumlanabilir. 1 $Doğru ve 0 $Yanlış'tır. Bu durumda, tüm sayıların kalansız olarak 2'ye bölünmesi 0 değerini üretecektir. Ancak ifade True olduğunda nesneleri iletmek için Where-Object'e ihtiyacım var, bu yüzden değeri tersine çevirmek için -Not operatörünü kullanıyorum. Böylece Yanlış, Doğru olur ve sayı, toplamı alabileceğim Measure-Object'e iletilir. Ve evet, Where-Object ifadesini yazmanın başka yolları da var.

Her Döngü İçin

Üçüncü fikrim ForEach döngüsünü kullanmaktı.

$t = 0
foreach ($n in (1..100)) {
    if ($n/2 -is [int]) {
        $t += $n
    }
}
$t

Kod şöyle diyor: "Koleksiyondaki her şey için bir şeyler yapın." Koleksiyon 1..100 aralığındadır. “Şey”e istediğiniz herhangi bir şey denilebilir. Benim kodumda bu $n'dir. Her değer için $n/2'nin [int] türünde bir nesne olup olmadığını test etmek için bir IF ifadesi kullanıyorum. 1,5 gibi bir değer teknik olarak bir [çift] değerindedir. Değerin testi geçtiğini varsayarsak, $t değerini bu değer kadar artırırım.

Bonus

"Doğru" çözüm, kodunuzun geri kalanına ve neyin en anlamlı olduğuna bağlıdır. Bu yüzden öğrenebileceğiniz farklı teknikler var. Eğer basit, tek satırlık bir çözüm isteseydim şöyle bir şey yapabilirdim:

(1..100 | where-object {$_/2 -is [int]} | measure-object -sum).sum

Ölçüm nesnesini almak yerine sadece sum özelliğini alıyorum. ( ) PowerShell'e "bu kodu çalıştırın ve nesneleri tutun" der. Bu, bunun tek satırlık bir versiyonudur:

$m = 1..100 | where-object {$_/2 -is [int]} | measure-object -sum
$m.sum

() ile $m örtülü olarak tanımlanıyor.

Orta seviye

Bir sonraki seviyedeki zorluk, 1 ile kullanıcı tarafından belirlenen maksimum değer arasındaki her X değerinin toplamını alacak bir fonksiyon yazmaktı. Ayrıca ortalamayı almamız ve ideal olarak hesaplamada kullanılan tüm sayıları dahil etmemiz istendi.

Geliştirme amacıyla her zaman basit kodla başlarım. 1 ile 100 arasındaki her 6 sayının toplamını almayı test ettim.

$total = 0
$max = 100
$int = 6
$count = 0
$Capture = @()

for ($i = $int; $i -le $max; $i += $int) {
    $count++
    $Capture += $i
    $total += $i
}
[pscustomobject]@{
    Start    = 1
    End      = $Max
    Interval = $int
    Sum      = $total
    Average  = $total/$count
    Values   = $Capture
}

Benzer operatörleri ve teknikleri kullandığımı göreceksiniz. Yeni adım, anında özel bir nesne yaratıyorum. Hashtable anahtarları özellik adları haline gelir.

Start    : 1
End      : 100
Interval : 6
Sum      : 816
Average  : 51
Values   : {6, 12, 18, 24...}

Bununla bu fonksiyonu yaratmayı başardım.

Function Get-NFactorial {

    [CmdletBinding()]

    Param  (
        [Parameter(Position = 0)]
        [int32]$Start = 1,
        [Parameter(Mandatory, Position = 1)]
        [int32]$Maximum,
        [Parameter(Mandatory)]
        [ValidateRange(1, 10)]
        [ArgumentCompleter({1..10})]
        [int32]$Interval
    )

    Begin {
        Write-Verbose "Starting $($MyInvocation.Mycommand)"
        Write-Verbose "Getting NFactorial values between $start and $Maximum"
        Write-Verbose "Using an interval of $Interval"

        #initialize some variables
        $count = 0
        $Capture = @()
        $Total = 0
    } #begin

    Process {
        Write-Verbose "Looping through the range of numbers"
        for ($i = $Interval; $i -le $Maximum; $i += $Interval) {
            $count++
            $Capture += $i
            $Total += $i
        }
        Write-Verbose "Writing result to the pipeline"
        [pscustomobject]@{
            PSTypeName  = "nFactorial"
            Start       = $Start
            End         = $Maximum
            Interval    = $Interval
            Sum         = $Total
            Average     = $total/$count
            Values      = $Capture
            ValuesCount = $Capture.Count
        }
    } #process

    End {
        Write-Verbose "Ending $($MyInvocation.Mycommand)"
    } #end

} #close function

Interval parametresi için daha önce görmediğiniz bir şey yapıyorum. Kullanıcıyı 1 ile 10 arasında bir aralık değeri kullanacak şekilde sınırlandırıyorum. Bu, daha önce görmüş olabileceğiniz ValidateRange özelliğidir. Diğer öğe ise ArgumentCompleter'dır. Birisi işlevi çalıştırdığında, -Interval değerini sekmeyle tamamlayabilmesini istiyorum. ValidateSet'i kullandığınızda sekme tamamlamanın çalıştığını fark ettim. Ancak ValidateRange ile değil. (Evet, ValidateSet'i kullanabilirdim ancak o zaman size öğretecek hiçbir şeyim olmazdı!). ArgumentCompleter, {} içindeki kodun sonuçlarını otomatik tamamlama değerleri olarak kullanır.

İşte işlev çalışıyor.

PS C>\> Get-Nfactorial -Start 1 -Maximum 100 -Interval 2

Start       : 1
End         : 100
Interval    : 2
Sum         : 2550
Average     : 51
Values      : {2, 4, 6, 8...}
ValuesCount : 50

Bu konuda bir adım daha atalım.

Sonuçları Biçimlendirme

Varsayılan çıktı bir listedir ve belki de bu özelliklerin tümünü varsayılan olarak görmeme gerek yoktur. Dikkat ettiyseniz özel nesne karma tablosunda PSTypeName adında bir özellik tanımladım. Özel biçimlendirme yapabilmek için nesnenizin benzersiz bir tür adına ihtiyacı vardır. Benimkinin adı nFactorial.

Biçimlendirme bir ps1xml dosyası gerektirecektir. Ama korkmayın. PowerShell Galerisinden PSScriptTools modülünü yükleyin ve New-PSFormatXML'i kullanın.

Get-Nfactorial -Start 1 -Maximum 100 -Interval 2 | New-PSFormatXML -Path .\nfactorial.format.ps1xml -Properties Start,End,Interval,Sum,Average -FormatType Table

Dosyayı düzenleyebilir ve gerektiği gibi ayarlayabilirsiniz. İşte benim versiyonum.

<?xml version="1.0" encoding="UTF-8"?>
<!--
format type data generated 05/19/2020 10:06:19 by BOVINE320\Jeff
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 05/19/2020 10:06:19 by BOVINE320\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>nFactorial</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.
        <AutoSize /> 
        -->
        <TableHeaders>
          <TableColumnHeader>
            <Label>Start</Label>
            <Width>5</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>End</Label>
            <Width>6</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Interval</Label>
            <Width>8</Width>
            <Alignment>center</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Sum</Label>
            <Width>9</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Average</Label>
            <Width>10</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Start</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>End</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Interval</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>"{0:n0}" -f $_.Sum</ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Average</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

Kullanmak için PowerShell'i güncellemeniz gerekir.

Update-FormatData .\nfactorial.format.ps1xml

Bunu yerine getirdiğimde artık güzel biçimlendirilmiş çıktılar alıyorum.

Unutmayın, bu yalnızca PowerShell'e başka bir şey yapmasını söylemezsem yeni varsayılanımdır. Hala işlevi çalıştırabilir ve Select-Object veya Format-List'e aktarabilirim.

Bu aptalca işlevin pratik olmaktan uzak olduğu açık, ancak bu teknikleri ve kalıpları gelecekteki projelerde kullanabilirim. Umarım bundan bir sonuç çıkarmışsınızdır ve sizi Iron Scripter zorluklarıyla karşı karşıya kaldığınızda mücadele etmeye teşvik ediyorum.

İlgili makaleler