Başka Bir PowerShell Matematik Sorununu Çözmek


Geçen ay, Iron Scripter Başkanı "eğlenceli" bir PowerShell komut dosyası oluşturma yarışması yayınladı. Aslında matematikle ilgili birkaç zorluk. Tüm bu zorluklarda olduğu gibi, sorunu çözmek için kullandığınız teknikler ve kavramlar sonuçtan daha önemlidir. Sorunlara şu şekilde yaklaştım.

Sorun #1

İlk zorluk, 2568 gibi bir sayıyı alıp, tek tek tam sayıların toplamını bulmaktı.

2+5+6+8

Cevap 21'dir. Muhtemelen dizeyi ayrı ayrı öğelere ayırmanın birkaç yolunu düşünebilirsiniz. Düzenli ifadeler kullanmaya karar verdim.

[int]$i = 12931

#define a regex pattern to match a single digit
[regex]$rx="\d{1}"

Her normal ifade eşleşme nesnesi değeri ayrı bir sayı olacaktır. Toplamı elde etmek için değerleri kolayca Measure-Object'e aktarabilirim.

#get all matches and add them up
$measure = $rx.Matches($i) | Measure-Object -Property Value -Sum

#write the sum to the pipeline
$measure.sum

Bunu bir fonksiyona dönüştürecek kadar kolaydır.

Function Get-NumberSum {

    [cmdletbinding()]
    [OutputType([Int])]
    Param(
        [Parameter(Position = 0, Mandatory, HelpMessage = "Enter an integer value like 1234")]
        [Alias("i")]
        [int]$Value
    )

    #define a regex pattern to match a single digit
    [regex]$rx = "\d{1}"

    #get all matches and add them up
    $measure = $rx.Matches($Value) | Measure-Object -Property Value -Sum

    #write the sum to the pipeline
    $measure.sum
}

Fonksiyon bir tamsayı istiyor. Ancak düzenli ifade modeli, içinde sayı bulunan her şey üzerinde çalışır.

($rx.matches("A12B9C31D") | measure value -sum).sum

Bu bana 16 ile aynı cevabı veriyor. Mücadelenin bazı bonus unsurları vardı, işte fonksiyonun biraz daha gelişmiş bir versiyonu.

Function Get-NumberSum {
    [cmdletbinding()]
    [OutputType([Int])]
    [OutputType("NumberSum")]
    [Alias("gns")]
    Param(
         [ValidateScript({
            if ($_.ToString().Length -le 10) {
                $True
            }
            else {
                Throw "Specify a value of 10 digits or less."
                $False
            }
        })]
        [int64]$Value,
        [switch]$Quiet
    )

    Write-Verbose "Processing $Value"
    #define a regex pattern to match a single digit
    [regex]$rx = "\d{1}"

    $values = $rx.Matches($Value).Value
    Write-Verbose ($values -join "+")

    $measure =  $Values | Measure-Object -Sum
    Write-Verbose "Equals $($measure.sum)"

    if ($Quiet) {
        $measure.sum
    }
    else {
        [pscustomobject]@{
            PSTypeName = "NumberSum"
            Value = $Value
            Elements = $values
            Sum = $measure.sum
        }
    }
}

Bu sürüm varsayılan olarak özel bir nesne oluşturacaktır. -Quiet parametresi yalnızca sonucu gösterir. Parametre doğrulaması çeşitli şekillerde yapılabilir. $Value tipini [int] olarak tutsaydım, sayı zaten 10 rakamdan fazla olamaz. Ama farklı bir şey denemek istedim. Bu yüzden özel bir hata mesajı görüntülemek için ValidateScript özelliğini kullanıyorum. İşlev takma adını kullanmanın nasıl göründüğü aşağıda açıklanmıştır.

Tamam, normal ifade kullanmanın bu meydan okuma için biraz fazla abartıldığını kabul ediyorum ama yine de eğlenceli. İşte daha da basit bir yol.

($i -split "" | measure-object -sum).sum

Sorun #2

İkinci problem ise biraz daha zorluydu. Bir sayı dizisi verildiğinde, olası tüm benzersiz toplamlar nelerdir?

$a = 2,5,6

2
5
6
2+5 = 7
2+5+6 = 13
2+6 = 8
5+6 = 11

Bu problem, dizideki sayıların toplamının alınmasını ve olası tüm kombinasyonların dikkate alınmasını gerektirir. İşin püf noktası, kombinasyon halinde test edilecek sayıları azaltarak işlevi yinelemeli olarak çağırmak.

function Get-ReductiveSum {
    param([array]$Numbers,[int]$Index=0,[int]$Sum=0)

    if ($numbers.count -eq $index) {
        $sum
    }
    else {
        Get-ReductiveSum $numbers -index ($index+1) -sum ($sum+$numbers[$index])
        Get-ReductiveSum $numbers ($index+1) $sum
    }
}

Fonksiyonu bu şekilde kullanabilirim.

Mücadele hedeflerine ulaşmak ve bunun kullanımını kolaylaştırmak için bu "ebeveyn" işlevini oluşturdum.

Function Get-PossibleSum {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateRange(1,9)]
        [int[]]$Values
    )

    #nested function
    function Get-ReductiveSum {
        param([array]$Numbers,[int]$Index=0,[int]$Sum=0)

        Write-Verbose "Get-ReductiveSum -Numbers $($numbers -join ',') -index $index -sum $sum"
        if ($numbers.count -eq $index) {
            Write-Verbose "Found sum $sum"
            $sum
        }
        else {
            Write-Verbose "Reducing numbers to $($numbers -join ',')"
            Write-Verbose "Setting index to $($index+1)"
            Write-Verbose "Get-ReductiveSum $($sum+$numbers[$index])"
            Get-ReductiveSum $numbers -index ($index+1) -sum ($sum+$numbers[$index])
            Write-Verbose "Get-ReductiveSum $sum"
            Get-ReductiveSum $numbers ($index+1) $sum
        }
    }

    Write-Verbose "Using values $($values -join ',')"
    Write-Verbose "Verifying unique values"
    $values = $values | Get-Unique
    if ($values.count -gt 9) {
        #this should probably never happen
        Write-Warning "You specified $($values.count) values. Only using the first 9"
        $Values = $Values[0..8]
    }
    Write-Verbose "Calculating possible unique sums for $($values -join ',')"
    $result  =  Get-ReductiveSum $Values | Where-Object {$_ -gt 0}
    Write-Verbose "Found $($result.count) non-zero sums"
    $result | Sort-Object
}

Verbose çıktısını görmek için kodu denemenize izin vereceğim.

Beyninizin PowerShell yöntemini düşünmesini sağlamak zor olabilir. Bu yüzden bu zorluklar çok faydalıdır. PowerShell boru hattını kafanızda ne kadar çok takip ederseniz, kullanımı o kadar kolay olacaktır.

İlgili makaleler