4
\$\begingroup\$

I am trying to create a function for a larger script to determine the third octet of a site IP address based off a user entered site number. I have actually come up with the function, but was hoping that someone could help me come up with something that is simpler and more elegant, instead of the multitude of nested if statements that I came up with. Here are the rules:

  • For sites numbered 0001 - 0255, the third octet is the last 3 digits of the site number, less any zeroes preceding a real number (e.g. Site 0007 = 10.10.7.xxx)
  • For sites numbered 0256 - 0399 and 0500 - 1999 (except 1202), the third octet is the last 2 digits of the site number less any zeroes preceding a real number (e.g. site 0315 = 10.22.15.xxx)
  • For sites numbered 401 - 499, the third octet is the last two digits of the site number +100 (e.g. 0401 = 10.20.101.xxx)
  • Site 1202 is an exception to the above rule, where the third octet is the last 3 digits (.202)
  • For sites 5000 and above (5000 - 9999), the first digit is dropped, and then the rules above are followed. (e.g. 8248 = 10.12.48.xxx)

Here is what I have come up with. It is not very elegant, but it seems to work. Note that the first three lines will be in another function in the script, but are here for testing purposes.

$global:site = Read-Host 'What is the 4 digit site number?' #must include leading zeros
[int]$global:sitecheck=$site #need for comparasion operators
[string]$global:sitestring=$site

if ($sitecheck -le 255)
    {
    if ($sitestring.substring(0,3) -eq '000') #(i.e. 0001 - 0009)
        {
        $global:thirdoct = $sitestring.substring(3,1) #(i.e. 1-9)
        }
        elseif ($sitestring.substring(0,2) -eq '00') #(i.e. 0010 - 0099) 
            {
            $global:thirdoct = $sitestring.substring(2,2) #(i.e. 10 - 99)
            }
            elseif ($sitestring.substring(0,1) -eq '0') #(i.e. 0100 - 0255)
            {
            $global:thirdoct = $sitestring.substring(1,3) #(i.e. 100 - 255)
            }
}           
elseif ($sitecheck -ge 256 -and $sitecheck -le 399 ) 
    {
    if ($sitestring.substring(2,1) -eq '0') 
        {
        $global:thirdoct = $sitestring.substring(3,1)
        }
        else
        {
        $global:thirdoct = $sitestring.substring(2,2)
        }
}
elseif ($sitecheck -ge 401 -and $sitecheck -le 499) 
    {
    $global:thirdoct = 100+$sitestring.substring(2,2)
    }
elseif ($sitecheck -ge 501 -and $sitecheck -le 799) 
    {
    if ($sitestring.substring(2,1) -eq '0')
        {
        $global:thirdoct = $sitestring.substring(3,1)
        }
        else
        {
        $global:thirdoct = $sitestring.substring(2,2)
        }
    }
elseif ($sitecheck -eq 1202)
    {
    $global:thirdoct = $sitestring.substring(1,3)
    }
elseif ($sitecheck -ge 5000 -and $sitecheck -le 9999) 
    {
    $site5000 = [int]$sitestring.substring(1,3)
    if ($site5000 -le 255)
       {
       if ($sitestring.substring(1,2) -eq '00')
           {
           $global:thirdoct = $sitestring.substring(3,1)
           }
                elseif ($sitestring.substring(1,1) -eq '0')
                {
                $global:thirdoct = $sitestring.substring(2,2)
                }
                    else 
                    {
                    $global:thirdoct = $sitestring.substring(1,3)
                    }
    }
    elseif (($site5000 -ge 256 -and $site5000 -le 399) -or ($site5000 -ge 501 -and $site5000 -le 999))
        {
        $global:thirdoct = $sitestring.substring(2,2)
        }
    elseif ($site5000 -ge 401 -and $site5000 -le 499)
        {
        $global:thirdoct = 100+$sitestring.substring(2,2)
        }
    }
\$\endgroup\$
  • \$\begingroup\$ if you do a modulus division by 100 you don't need to distinguish between one or two places $global:thirdoct = [int]$sitestring % 100 And I`d use a switch instead of the nested if/elseif to handle the exceptions and leave the rest to the default. \$\endgroup\$ – LotPings Mar 22 '19 at 3:13
4
\$\begingroup\$

Let's start with a table of inputs and outputs, because it may not be exactly the same as what you expect from your script. The first column is the input range, the other column is the transformation applied for those inputs.

0000-0255       self
0256-0399       last2
0400            undefined
0401-0499       -300
0500            undefined
0501-0799       last2
0800-1201       undefined
1202            last3
1203-4999       undefined
5000-5255       last3
5256-5399       last2
5400-5500       undefined
5501-5999       last2
6000+           same as 5000-5999

The best way to manage these cases is with a data structure. A switch statement can work too but there's going to be a lot of repetition, and you really want something that lets you focus on the data.

In this version, the ranges are an array of arrays. Each inner array is a lower bound, an upper bound, and a script block that transforms input to output. As a special case, a negative result from the script block means "restart the conversion with this new value (after making it positive)"—this lets us reuse the low patterns for most of 5000-5999, and then reuse the 5000-5999 pattern for 6000-9999.

We simply traverse the array looking matches, and apply the transforms from matching rows. (There's only ever one matching row per call to convert, in this example, but the code allows overlapping ranges; matching transforms will apply in order).

$ranges = @(
    @(    0,  255, { $_ }                  ),
    @(  256,  399, { $_ % 100 }            ),
    @(  401,  499, { $_ - 300 }            ),
    @(  501,  799, { $_ % 100 }            ),
    @( 1202, 1202, { $_ % 1000 }           ),
    @( 5000, 5399, { -($_ % 1000) }        ), # redo using last 3 digits
    @( 5501, 5999, { -($_ % 1000) }        ),
    @( 6000, 9999, { -($_ % 1000 + 5000) } )  # redo using 5000 + last 3
)

$convert = {
    $_ = [int]$args[0]
    $out = -1
    foreach ($range in $ranges) {
        if ($_ -ge $range[0] -and $_ -le $range[1]) { 
            $out = & $range[2] 
            # negative result means re-do the conversion with a new value
            if ($out -lt 0) { $out = & $convert ($out * -1) }
        }
    }
    return $out
}

$site = Read-Host 'enter the site code: '
$octet = & $convert $site
if ($octet -ge 0) {
    echo $octet
} else {
    echo "error - $site has no defined octet"
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Nice one +1, I do see a difference in repetitions to a switch as there you can break if a condition is met while the foreach will iterate inevitable through all (non matching) ranges. \$\endgroup\$ – LotPings Mar 22 '19 at 10:14
  • \$\begingroup\$ that's entirely optional: you could move return $out inside of the loop and return on the first match. I think this way is slightly better: makes it easy to have one-off exceptions, like if 400-499 does one thing but 411 and 455 do something different, have a 400-499 range, and below it specific overrides for the exceptions. \$\endgroup\$ – Oh My Goodness Mar 22 '19 at 11:09
0
\$\begingroup\$

See the following as an incomplete and untested template, ATM too tired to distinguish between parens and curly braces.

$global:site = Read-Host 'What is the 4 digit site number?' #must include leading zeros
[int]$global:sitecheck=$site #need for comparasion operators
[string]$global:sitestring=$site

switch ($sitecheck){
    {$_ -le 255}     {$global:thirdoct = $_ % 1000;Break} 

    {$_ -ge 256 -and 
     $_ -le 399}     { ;Break}

    {$_ -ge 401 -and 
     $_ -le 499}      {$global:thirdoct = 100 + $_ % 100;Break} 

    {$_ -eq 1202}     {$global:thirdoct = $_ % 1000;Break} 

    {$_ -ge 501 -and 
     $_ -le 799}      {} 

    {$_ -ge 5000 -and
     $_ -le 9999}    { ;Break}

     default         {  }
}

"Site:{0} thirdOct: {1}" -f $Sitecheck,$thirdOct
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.