Búsqueda de sitios web

Resolviendo otro desafío matemático de PowerShell


El mes pasado, el presidente de Iron Scripter publicó un desafío de secuencias de comandos de PowerShell "divertido". En realidad, algunos desafíos relacionados con las matemáticas. Como ocurre con todos estos desafíos, las técnicas y conceptos que utilice para resolver el desafío son más importantes que el resultado en sí. Así es como abordé los problemas.

Problema #1

El primer desafío fue tomar un número, como 2568, y obtener la suma total de los números enteros individuales.

2+5+6+8

La respuesta es 21. Probablemente se te ocurran algunas formas de dividir la cadena en elementos individuales. Decidí usar expresiones regulares.

[int]$i = 12931

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

Cada valor de objeto de coincidencia de expresión regular será el número individual. Puedo canalizar fácilmente los valores a Medir-Objeto para obtener la suma.

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

#write the sum to the pipeline
$measure.sum

Esto es bastante fácil de convertir en una función.

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
}

La función quiere un número entero. Pero el patrón de expresión regular funciona con cualquier cosa que contenga números.

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

Esto me da la misma respuesta de 16. El desafío tenía algunos elementos adicionales, así que aquí hay una versión un poco más avanzada de la función.

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
        }
    }
}

Esta versión creará un objeto personalizado de forma predeterminada. El parámetro -Quiet muestra solo el resultado. La validación de parámetros se puede manejar de varias maneras. Si mantuve el tipo de $Value como [int], el número no puede tener más de 10 dígitos de todos modos. Pero quería probar algo diferente. Entonces estoy usando un atributo ValidateScript para mostrar un mensaje de error personalizado. Así es como se ve usando el alias de función.

Ok, admito que usar una expresión regular es un poco excesivo para este desafío, pero es divertido. Aquí tienes una forma aún más sencilla.

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

Problema #2

El segundo problema fue un poco más desafiante. Dada una serie de números, ¿cuáles son todas las sumas únicas posibles?

$a = 2,5,6

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

Este problema requiere obtener la suma de números de la matriz y tener en cuenta todas las combinaciones posibles. El truco consiste en llamar recursivamente a la función, reduciendo los números a probar en combinación.

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
    }
}

Puedo usar la función como esta.

Para cumplir con los objetivos del desafío y hacerlo fácil de usar, creé esta función "principal".

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
}

Te dejaré probar el código para ver el resultado detallado.

Hacer que su cerebro piense a la manera de PowerShell puede ser difícil. Por eso estos desafíos son tan útiles. Cuanto más pueda seguir el proceso de PowerShell en su cabeza, más fácil le resultará utilizarlo.

Artículos relacionados