How would you generate a unique sequence from 000 to 9ZZ

Viewed 135

How would you generate a unique sequence from 000 to 9ZZ. Lastly, my export-csv is not working. Please see the data and export output below.

The alphanumeric sequence starts from 0 through 9 and then A through Z.

Please be advised, my PowerShell skill are a bit new. :)

$i = @()
$a = 0..9
$b = 65..90 | Foreach{[Char]$_}
$i = $a + $b
For($d = 0; $d -le $i.count; $d++){
    $g = $i[$d]

    For($e = 0; $e -le $i.count; $e++){
        $h = $i[$e]

        For($f = 0; $f -le $i.count; $f++){
            $j = $i[$f]

            $k = "{0}{1}{2}" -f $g, $h, $j
            $k #| Export-Csv -Path .\List.csv -NoTypeInformation -Append

            If($k -eq '9ZZ'){
                Break
            }
        }
    }
}

Data Output:
000
001
.
.
|
V
009
00A
.
.
00Z
00 <-- I don't get this.
010

Export:
Length
3 <-- I don't get this either.
.
.
|
v
3

Any and all help is appreciated. Thank you in Advanced. ;)

4 Answers

I would do it this way, with 3 foreach loops and a labeled break:

$digits = 0..9
$chars = (65..90).ForEach([char])
$dict = $digits + $chars

$result = :outer foreach($i in $dict)
{
   foreach($x in $dict)
   {
      foreach($z in $dict)
      {
         '{0}{1}{2}' -f $i,$x,$z
         if($i -eq 9 -and $x -eq 'Z' -and $z -eq 'Z')
         {
            break outer
         }
      }
   }
}

$result | Export-Csv ...

This is a classic off-by-1 error - your loops run from 0 through $i.count (included), which is exactly 1 longer than $i.

Change -le $i.count to -lt $i.count in all 3 loop conditions and it'll work.


You could simplify your code by generating the full range of digits/characters up front a little differently, and then use 3 nested foreach loops instead:

$digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.ToCharArray()

$ranges = 
  :outerLoop
  foreach($a in $digits){
    foreach($b in $digits){
      foreach($c in $digits){
        # save and output new value
        ($label = "${a}${b}${c}")
        # exit the label generation completely if we've reached the desired upper boundary
        if($label -eq '9ZZ'){ break outerLoop }
      }
    }
  }

$ranges now contain the correct range of labels from 000 through 9ZZ

To complement the helpful existing answers with a generalized solution for producing permutations of characters with a given number of places, using recursive helper function Get-Permutations:

function Get-Permutations {
  param(
    [Parameter(Mandatory)]
    [string] $Chars,        # e.g. '0123456789'
    [Parameter(Mandatory)]
    [uint] $NumPlaces       # e.g. 2, to produce '00', '01', ..., '99'
  )
  
  switch ($NumPlaces) {
    0 { return }
    1 { [string[]] $Chars.ToCharArray() }
    default {
      Get-Permutations -Chars $Chars -NumPlaces ($NumPlaces-1) | ForEach-Object {
        foreach ($c in $chars.ToCharArray()) { $_ + $c }
      }
    }
  }
}

You'd call it as follows (to get '000', '001', ..., but all the way up to 'ZZZ' - you can post-filter with ... | Where-Object { $_ -le '9ZZ' })

Get-Permutations '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' 3

Note:

  • In PowerShell (Core) 7+ you can create the string of characters with
    -join ('0'..'9' + 'A'..'Z')
  • In Windows PowerShell, where you cannot use [char] instances with the range operator, .., you can use the following, more concise alternative to your approach:
    -join ([char[]] (48..57) + [char[]] (65..90)), based on the chars.' code points obtained with, e.g. [int] [char] 'A'

similarly you could cheat since '009' -le '00A'

$sb = [System.Text.StringBuilder]::new()
function inc([string]$s){
   [byte[]]$v = $s.ToCharArray()
   $radix = $v.Count-1
   $carry = $true
   while($carry -and $radix -ge 0){
      $carry = $false
      switch(++$v[$radix]){
       91{$v[$radix--] = 48; $carry = $true}
       58{$v[$radix]   = 65}
      }
   }
   $null = $sb.clear()
   if($carry){$null = $sb.Append('1')}
   return $sb.Append([char[]]$v).ToString()
}
Describe 'Test inc function' {
   It 'increments 000' {inc '000' | should be '001'}
   It 'increments 001' {inc '001' | should be '002'}
   It 'increments 008' {inc '008' | should be '009'}
   It 'increments 009' {inc '009' | should be '00A'}
   It 'increments 00A' {inc '00A' | should be '00B'}
   It 'increments 00Y' {inc '00Y' | should be '00Z'}
   It 'increments 00Z' {inc '00Z' | should be '010'}
   It 'increments 010' {inc '010' | should be '011'}
   It 'increments 0ZZ' {inc '0ZZ' | should be '100'}
   It 'increments 1ZZ' {inc '1ZZ' | should be '200'}
   It 'increments ZZZ' {inc 'ZZZ' | should be '1000'}
}
measure-command {for($i = '000'; $i -le '9ZZ'; $i = inc $i){$i}}

&{for($i = '000'; $i -le '9ZZ'; $i = inc $i){
   $i
}} | set-content -Path .\List.csv

Edit: unnecessarily faster

Inspired by mklement0's awesome answer

$cache = @{}
function Get-Permutations{
   param(
       [Parameter(Mandatory=$true)][string]$Alphabet # e.g. '0123456789'
      ,[Parameter(Mandatory=$true)][uint32]$Length   # e.g. 2, to produce '00', '01', ..., '99'
   )
   if($Length -eq 0)
      {return}
   if($Length -eq 1)
      {return $Alphabet.ToCharArray()}
      
   if($Alphabet -NotIn $cache.Keys){
      $cache.$Alphabet = new-object String[][] $Length
      $cache.$Alphabet[1] = $Alphabet.ToCharArray()
   }elseif($cache.$Alphabet.Count -lt $Length){
      $tmp = new-object String[][] $Length
      [Array]::Copy($cache.$Alphabet,$tmp,$cache.$Alphabet.Count)
      $cache.$Alphabet = $tmp
   }

   Get-Permutations-Helper $Alphabet $Length
}
function Get-Permutations-Helper{
   param(
       [string]$Alphabet
      ,[uint32]$Length
   )
   $TailLength = $Length-1
   if($cache.$Alphabet[$TailLength] -eq $null){
      $cache.$Alphabet[$TailLength] = Get-Permutations-Helper $Alphabet $TailLength
   }
   foreach($head in $Alphabet.ToCharArray()){ 
      foreach($tail in $cache.$Alphabet[$TailLength]){ 
         $head + $tail
      }
   }
}
Describe 'Test Get-Permutations function' {
   It 'Makes an ordered ABC,2 permutation' {
      $valid = @('AA','AB','AC','BA','BB','BC','CA','CB','CC')

      $cache = @{}; $i = 0
      Get-Permutations 'ABC' 2 | foreach-object {
         $_ | should be $valid[$i++]
      }
   }

   It 'Extends the cache' {
      $cache = @{}
      $null = Get-Permutations 'ABC' 2
      $cache.'ABC'[1][0] | should be 'A'
      
      $cache.'ABC'[1][0] = 'test'
      $results = Get-Permutations 'ABC' 3
      $cache.'ABC'[2][0] | should be 'Atest'
      $results[0]        | should be 'AAtest'
   }
}

$digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$spin = 10
$ttl=$null;for($i=0;$i -lt $spin;$i++){
   $cache = @{}
   $ttl+=measure-command {
      Get-Permutations $digits 3
   }
};$ttl;$ttl.TotalMilliseconds/$spin
$ttl=$null;for($i=0;$i -lt $spin;$i++){
   $cache = @{}
   $ttl+=measure-command {
      $enu = (Get-Permutations $digits 3).GetEnumerator();
      while($enu.MoveNext() -and ($v = $enu.Current) -le '9ZZ'){$v}
   }
};$ttl;$ttl.TotalMilliseconds/$spin

$enu = (Get-Permutations $digits 3).GetEnumerator();
&{while($enu.MoveNext() -and ($v = $enu.Current) -le '9ZZ'){
   $v
}} | set-content -Path .\List.csv
Related