Thursday, September 8, 2016

Arrays and Indexers in PowerShell



Today's post is about Arrays and Indexers in PowerShell. Here below you will find a very easy to follow program, that demonstrate arrays and indexer, by implementing some simple tasks that will make you grasp the idea of those 2 features real quick. The main goal of this post, is not really teaching arrays because, come on, you probably already know "all" about them, in fact, it is more to show you how you do that in PowerShell, in this case, compared to all other ~22 languages on future posts, which essentially, is the real aim behind this blog.

By the way, if you missed my most recent post, "New Series - Arrays and Indexers", check it out. It has more details about the following program, and a bunch of definitions for the concepts used on this, and the following, posts. Or you can check my previous posts about arrays in C# and C++ just to compare.

I encourage you to copy the code below and try it yourself, normally, all programs you find in this blog are source code complete, just paste it on your IDE and run it.

There is room for improvement of the code, using generics is one example, but Generics, Collections, lambdas, etc. will have their own "series" of posts.

NOTE:
PowerShell provides two different types of arrays, PowerShell [array] objects and typed arrays [type[]]. In PowerShell array objects, a script can dynamically add and remove elements, and elements can be of any data type. In typed arrays, which are dense, the size is fixed, and elements must be the same type as the base type of the array, the least are coming from .NET types, specially multi-dimensional arrays.

I chose to use the Typed Arrays (and all other variables as well) to keep it closer to the previous posts. I used [array] for jagged arrays [array[]], and for function/method parameter even though [object[]] should also work also.


function ReverseChar($arr) {
    [char[]] $reversed = @(1..$arr.length)
    #[array]::Reverse($reversed)  <-- better to use this but for demo purposes I did it with a for 2 indexes
  
    for ($i, $j = 0, ($arr.length - 1); $j -ge 0; $i++, $j--) {
        $reversed[$i] = $arr[$j] 
    }
    return $reversed
}

function BubbleSort([array]$arr) {  
    $swap = 0  
    for ($i = ($arr.Length - 1); $i -gt 0; $i--) {  
        for ($j = 0; $j -lt $i; $j++) {  
            if ($arr[$j] -gt $arr[$j + 1]) {     
                $swap = $arr[$j]
                $arr[$j] = $arr[$j + 1]                
                $arr[$j + 1] = $swap                  
            }  
        }  
    }  
    return $arr  
}  

function TransposeMatrix([int[,]]$m) {  
    <# Transposing a Matrix 2,3  
        *  
        * A =  [6  4 24]T [ 6  1]  
        *      [1 -9  8]  [ 4 -9] 
        *                 [24  8] 
    #>
    [int[,]]$transposed = [int[,]]::new(
        $m.GetUpperBound(1) + 1,
        $m.GetUpperBound(0) + 1)
    
    for ($i = 0; $i -le $m.GetUpperBound(0); $i++) {  
        for ($j = 0; $j -le $m.GetUpperBound(1); $j++) {                          
            $transposed[$j,$i] = $m[$i,$j]
        }  
    } 
    return ,$transposed  # use comma operator to avoid unroll
}  

function UpperCaseRandomArray([array[]]$arr)  
{  
    #$r = new-object [System.Random]
    #$i = $r.Next(0, ($arr.Length - 1))
    $i = get-random -minimum -0 -maximum ($arr.Length - 1)
    for ($j = 0; $j -lt $arr[$i].Count; $j++) {
        $arr[$i][$j] = $arr[$i][$j].ToUpper()
    }
    return $arr
}  

function PrintArray([array]$arr) {
    if(!$arr) { $arr=@() }
    Write-Host("`nPrint Array Content $($arr.GetType().Name) [$($arr.Length)]")

    for ($i=0; $i -lt $arr.Length; $i++) {    
        Write-Host(" array [$($i.ToString().PadLeft(2))] = $($arr[$i].ToString().PadLeft(2))")
    }
}

function PrintMatrix([int[,]]$m) {  
    Write-Host ("`nPrint Matrix Content {0}[{0},{0}]" -f 
        $m.GetType().Name.Replace("[,]", ""),
        ($m.GetUpperBound(0) + 1), 
        ($m.GetUpperBound(1) + 1))
            
    for ($i = 0; $i -le $m.GetUpperBound(0); $i++) {
        for ($j = 0; $j -le $m.GetUpperBound(1); $j++) {
            Write-Host (" array [{0,2},{1,2}] = {2,2} " -f $i, $j, $m[$i,$j]) 
        }
    }
}  

function GraphJaggedArray([Array[]]$arr) {  
    <# When using Arrays, we can use foreach operator instead of for:  
        *  
        * for ($i = 0; $i -lt $arr.Length; $i++) 
        *   for ($j = 0; $j -lt $arr.Length; $j++)
        *  
        #> 
    Write-Host ("Print Text Content $($arr.GetType().Name)`n")  
    $lineCount = 1 
    foreach ($s in $arr) {  
        Write-Host -NoNewline ("Line{0,2}|" -f $lineCount)  
        foreach ($_ in $s) {  
            Write-Host -NoNewline ("{0,3}" -f '*')  
        }  
        Write-Host (" ($($s.Length))")
        $lineCount++ 
    }  
}

function PrintJaggedArray([array[]]$arr) {          
    Write-Host ("`nPrint Jagged Array Content $($arr.GetType().Name)")  
    for ($i = 0; $i -lt $arr.Count; $i++) {  
        $line = [System.Text.StringBuilder]::new()
        for ($j = 0; $j -lt $arr[$i].Count; $j++) {
            [void]$line.Append(" ")
            [void]$line.Append($arr[$i][$j].ToString())
        }
        if (($line.ToString()) -ceq ($line.ToString().ToUpper())) {
            [void]$line.Append(" <-- [UPPERCASED]")
        }
        Write-Host $line
    }  
}    


function PrintCommonArrayExceptions($arr) {      
    try {  

        if ($arr -eq $null) {             
            $arr[100] = "hola" 
        }
        elseif ($arr[0] -eq $null) {            
            $arr[100] = "hola"
        } 
        elseif ($arr[100] -eq $null) {            
            $arr[100][100] = "hola"
        }                   

        #throw [System.NullReferenceException]::new("")
        #throw [System.Exception]::new("")        
                            
    } 
    catch [System.Management.Automation.RuntimeException]{
        Write-Host ("`nException: `n$($_.Exception.GetType().FullName)`n$($_.Exception.Message)")          
    }
    catch [System.NullReferenceException],[System.IndexOutOfRangeException] {
        Write-Host ("`nException: `n$($_.Exception.GetType().FullName)`n$($_.Exception.Message)")  
    }               
    catch [Exception] {
        # pass
    }
    catch {
        # pass
    }
    finally {
        # pass
    }
}  

function PrintTitle([String]$message) {
    Write-Host 
    Write-Host ("=" * 55)
    Write-Host ($message)
    Write-Host ("=" * 55)
}

class Alphabet {
    # Array Field  
    hidden [char[]]$letters = @()     

    # No indexer support. 
    # Getter/Setter Method instead. 
    [char] GetItem([int]$index) {    
        return $this.letters[$index]
    } 

    [void] SetItem([int]$index, [char]$value) {
        $this.letters[$index] = [char]::ToUpper($value)
    }

    # "Read-Only Property"
    [int] Length() {    
        return $this.letters.Length
    } 

    # Constructors  
    Alphabet([int]$size) {
        $this.letters = @(,' ' * $size)
    }

    Alphabet([string]$list) {
        $this.letters = $list.ToUpper().ToCharArray()
    }

    Alphabet([char[]]$list) {
        $this.letters = $list
    }

    # Overridden Method  
    [string] ToString() {       
        return [String]::Join(",", $this.letters)
    }

    # Method 
    [char[]] Slice([int]$start, [int]$length) {
        [char[]]$result = @(,' ' * $length)
        for ($i, $j = 0, $start 
             $i -lt $length
             $i++, $j++) {  
            $result[$i] = $this.letters[$j]
        }  
        return $result
    }
}

# Single-dimensional Array(s)
printTitle("Reverse Array Elements")

# Declare and Initialize Array of Chars
[char[]] $letters = @(1..5)
$letters[0] = 'A'  
$letters[1] = 'E'  
$letters[2] = 'I'
$letters[3] = 'O'
$letters[4] = 'U'

# untyped arrays or an array of type [array] are all System.Object[]  
# typed arrays need an explicit type [int[]], [string[]], or even [Object[]]

PrintArray($letters)
$inverse_letters = ReverseChar($letters)
PrintArray($inverse_letters)

PrintTitle("Sort Integer Array Elements")

# Declare and Initialize Array of Integers
[int[]]$numbers = @(10, 8, 3, 1, 5)
PrintArray($numbers)
[int[]]$ordered_numbers = [int[]](BubbleSort($numbers))
PrintArray($ordered_numbers)

PrintTitle("Sort String Array Elements")

# Declare and Initialize and Array of Strings
[string[]]$names = @(
    "Damian",
    "Rogelio",
    "Carlos",
    "Luis",
    "Daniel"
)
PrintArray($names)
[string[]]$ordered_names = BubbleSort($names)
PrintArray($ordered_names) 


# Multi-dimensional Array (Matrix row,column)  
# To declare a multi-dimensional array in PowerShell 
            
PrintTitle("Transpose Matrix");  
            
<# Matrix row=2,col=3 
    * A =  [6  4 24] 
    *      [1 -9  8] 
    * #>
# $matrix = New-Object 'int[,]' 2,3
$matrix = [int[,]]::new(2,3)
$matrix[0,0], $matrix[0,1], $matrix[0,2] = 6, 4,24
$matrix[1,0], $matrix[1,1], $matrix[1,2] = 1,-9, 8 

PrintMatrix($matrix)  
[int[,]]$transposed_matrix = TransposeMatrix($matrix)
PrintMatrix($transposed_matrix)

# Jagged Array (Array-of-Arrays)
            
PrintTitle("Upper Case Random Array & Graph Number of Elements")
            
<#              
    * Creating an array of string arrays using the String.Split method 
    * instead of initializing it manually as follows: 
    *  
    * $text = @(  
    *      ,( "word1", "word2", "wordN" )
    *      ,( "word1", "word2", "wordM" )  
    *      ... 
    *      ) 
    *  
    * Text extract from: "El ingenioso hidalgo don Quijote de la Mancha" 
    *  
 #>  

[array[]]$text = @(   
  ,("Hoy es el día más hermoso de nuestra vida, querido Sancho;".Split(' '))  
  ,("los obstáculos más grandes, nuestras propias indecisiones;".Split(' '))  
  ,("nuestro enemigo más fuerte, miedo al poderoso y nosotros mismos;".Split(' '))
  ,("la cosa más fácil, equivocarnos;".Split(' '))
  ,("la más destructiva, la mentira y el egoísmo;".Split(' '))
  ,("la peor derrota, el desaliento;".Split(' '))
  ,("los defectos más peligrosos, la soberbia y el rencor;".Split(' '))
  ,("las sensaciones más gratas, la buena conciencia...".Split(' '))
)          

PrintJaggedArray($text)
UpperCaseRandomArray($text) > $null
PrintJaggedArray($text)
GraphJaggedArray($text)

# Array Exceptions  
            
PrintTitle("Common Array Exceptions")
            
PrintCommonArrayExceptions($null)  
PrintCommonArrayExceptions(@())
PrintCommonArrayExceptions($text)  
                  
# Accessing Class Array Elements through "Indexer" Getter/Setter    
PrintTitle('Alphabets');  

[Alphabet]$vowels = [Alphabet]::new(5)  
$vowels.setitem(0, 'a')  
$vowels.setitem(1, 'e')  
$vowels.setitem(2, 'i')  
$vowels.setitem(3, 'o')  
$vowels.setitem(4, 'u')  

Write-Host ("`nVowels = {" + [String]::Join(',', 
    $vowels.getitem(0), $vowels.getitem(1), $vowels.getitem(2), 
    $vowels.getitem(3), $vowels.getItem(4)) + "}")

[Alphabet]$en = [Alphabet]::new('abcdefghijklmnopqrstuvwxyz')
Write-Host ('English Alphabet = {' + $en.ToString() + "}")

Write-Host ('Alphabet Extract en[9..19] = {' + 
            ([Alphabet]::new($en.slice(9, 10))).tostring() + '}')
  

$word1 = [String]::Join("", $en.getItem(6), $en.getitem(14), 
                            $en.getitem(14), $en.getitem(3))
$word2 = [String]::Join("", $en.getitem(1), $en.getitem(24), 
                            $en.getitem(4), $en.getitem(4))
$word3 = [String]::Join("", $en.getitem(4), $en.getitem(21), 
                            $en.getitem(4), $en.getitem(17),
                            $en.getitem(24), $en.getitem(14), 
                            $en.getitem(13), $en.getitem(4))

Write-Host ("`n$word1 $word2, $word3!")

Read-Host


The output:






















































































VoilĂ , that's it. Next post in the following days.

3 comments:

  1. Hey Carlos,

    i am desperatly searching for a solution to transpone a matrix of chars.

    like

    Name Pos Title Street
    Chris Nu1 Geek MainRd
    Lou Nu10 Noob SmallRd

    to

    Name Chris Lou
    Pos Nu1 Nu10
    Title Geek Noob
    Street MainRd SmallRd

    But i just cant solve it.
    Its not about numbers, source is CSV / powershell multidimensional array...

    It seems like your function for transponing the array is very close, i just don't get it figuring out the solution :(

    Thanks in advance!

    ReplyDelete
    Replies

    1. You just have to change the parameter from [int[,]] to [string[,]] and increase the x and y size of the multidimensional array.


      function TransposeMatrix([string[,]]$m) {
      <# Transposing a Matrix 2,3
      *
      * A = ["6" "4" "24"]T [ "6" "1"]
      * ["1" "-9" "8"] [ "4" "-9"]
      * ["24" "8"]
      #>
      [string[,]]$transposed = [string[,]]::new(
      $m.GetUpperBound(1) + 1,
      $m.GetUpperBound(0) + 1)

      for ($i = 0; $i -le $m.GetUpperBound(0); $i++) {
      for ($j = 0; $j -le $m.GetUpperBound(1); $j++) {
      $transposed[$j,$i] = $m[$i,$j]
      }
      }
      return ,$transposed # use comma operator to avoid unroll
      }


      function PrintMatrix([string[,]]$m) {
      Write-Host ("`nPrint Matrix Content {0}[{0},{0}]" -f
      $m.GetType().Name.Replace("[,]", ""),
      ($m.GetUpperBound(0) + 1),
      ($m.GetUpperBound(1) + 1))

      for ($i = 0; $i -le $m.GetUpperBound(0); $i++) {
      for ($j = 0; $j -le $m.GetUpperBound(1); $j++) {
      Write-Host (" array [{0,2},{1,2}] = {2,2} " -f $i, $j, $m[$i,$j])
      }
      }
      }

      <#
      Name Pos Title Street
      Chris Nu1 Geek MainRd
      Lou Nu10 Noob SmallRd
      #>

      $matrix = [string[,]]::new(3,4) # or = New-Object 'string[,]' 3, 4

      $matrix[0,0], $matrix[0,1], $matrix[0,2], $matrix[0,3] = "Name", "Pos", "Title", "Street"
      $matrix[1,0], $matrix[1,1], $matrix[1,2], $matrix[1,3] = "Chris", "Nu1", "Geek", "MainRd"
      $matrix[2,0], $matrix[2,1], $matrix[2,2], $matrix[2,3] = "Lou", "Nu10", "Noob", "SmallRd"

      PrintMatrix($matrix)
      [string[,]]$transposed_matrix = TransposeMatrix($matrix)
      PrintMatrix($transposed_matrix)

      Delete
    2. If you are using jagged arrays instead of multidimensional arrays then the transposed code will not work as-is.

      multi-dimensional array = [object[x,y]]
      jagged array = object[[]]


      $array2 = New-Object 'object[,]' 10,20
      Write-Host ("{0}" -f $array2.GetType().Name)

      $array3 = @(@())
      Write-Host ("{0}" -f $array3.GetType().Name)

      Delete