Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Solving the PowerShell Counting Challenge

Posted on May 27, 2020May 27, 2020

A few weeks ago, an Iron Scripter PowerShell scripting challenge was posted. As with all of these challenges, the process is more important than the end result. How you figure out a solution is how you develop as a PowerShell professional. A few people have already shared their work. Today, I thought I'd share mine.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Beginner

The beginner challenge was to get the sum of even numbers between 1 and 100. You should be able to come up with at least 3 different techniques.

For Loop

My first solution is to use a For loop.

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

The variable $t is set to 0. This will be the total value. The syntax of the For loop says, "Start with $i and a value of 2 and keep looping while $i is less than or equal to 100. Every time you loop, increase the value of $i by 2." The += operator is a shortcut way of saying $i = $i+2. This should get all even numbers up to and including 100. I'm using the -le operator and not the -lt operator. Each time through the loop, the code inside the { }, I'm incrementing $t by the value of $i. In the end, I get a value of 2550 for $t.

Modulo Operator

The next technique is to use the Modulo operator - %.

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

The first part of the pipelined expression is using the Range operator to get all numbers between 1 and 100. Each number is piped to Where-Object which uses the Modulo operator. I'm dividing each number by 2. If there is a remainder, for example, 3/2 is 1.5, the result is 1. Otherwise, the operator produces a result of 0. 1 and 0 can also be interpreted as boolean values. 1 is $True and 0 is $False. In this case, all the numbers divided by 2 with no remainder will produce a value of 0. But I need Where-Object to pass objects when the expression is True so I use the -Not operator to reverse the value. Thus False becomes True and the number is passed to Measure-Object where I can get the sum. And yes, there are other ways you could have written the Where-Object expression.

ForEach Loop

My third idea was to use a ForEach loop.

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

The code says, "Foreach thing  in the collection, do something." The collection is the range 1..100. The "thing" can be called anything you want. In my code, this is $n.  For each value, I'm using an IF statement to test if $n/2 is an object of type [int]. A value like 1.5 is technically a [double]. Assuming the value passes the test, I increment $t by that value.

Bonus

The "right" solution depends on the rest of your code and what makes the most sense. That's why there are different techniques for you to learn.  If I just wanted a simple one-line solution, I could do something like this:

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

Instead of getting the measurement object, I'm getting just the sum property. The ( ) tell PowerShell, "run this code and hold on to the object(s)".  This is a one-line version of this:

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

With the (), $m is implicitly being defined.

Intermediate

The next level challenge was to write a function that would get the sum of every X value between 1 and a user-specified maximum. We were also asked to get the average and ideally include all the numbers that were used in the calculation.

For development purposes, I always start with simple code. I tested getting the sum of every 6th number between 1 and 100.

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

You'll see that I'm using similar operators and techniques. The new step is that I am creating a custom object on the fly. The hashtable keys become the property names.

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

With this, I was able to create this function.

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

For the Interval parameter, I'm doing something you may not have seen before. I'm limiting the user to using an interval value between 1 and 10. That's the ValidateRange attribute you may have seen before. The other element is an ArgumentCompleter. When someone runs the function, I want them to be able to tab-complete the value for -Interval. I've noticed that when you use ValidateSet, tab completion works. But not with ValidateRange. (Yes, I could have used ValidateSet,   but then I wouldn't have anything to teach you!). The ArgumentCompleter uses the results of the code inside the {} as autocomplete values.

Here's the function in action.

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

Let's take this one more step.

Formatting Results

The default output is a list and maybe I don't really need to see all of these properties by default. If you noticed, in the custom object hashtable I defined a property called PSTypeName. In order to do custom formatting, your object needs a unique type name. Mine is called nFactorial.

Formatting is going to require a ps1xml file. But don't freak out. Install the PSScriptTools module from the PowerShell Gallery and use New-PSFormatXML.

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

You can edit the file and adjust it as needed. Here's my version.

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

To use, you need to update PowerShell.

Update-FormatData .\nfactorial.format.ps1xml

With this in place, I now get nicely formatted output.

nfactorial

Remember, this is only my new default if I don't tell PowerShell to do anything else. I can still run the function and pipe to Select-Object or Format-List.

This silly function is clearly far from practical, but I can use these techniques and patterns in future projects. I hope you got something out of this and I encourage you to tackle the Iron Scripter challenges as they come along.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

2 thoughts on “Solving the PowerShell Counting Challenge”

  1. Pingback: ICYMI: PowerShell Week of 29-May-2020 | PowerShell.org
  2. Pingback: ICYMI: PowerShell Week of 29-May-2020 – 247 TECH

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d