Sunday, January 23, 2011

Multidimensional arrays in Powershell

Today I want to talk a little about multidimensional arrays in Windows Powershell. Multidimensional arrays are one of the complex data types supported by Powershell. They can be used to dynamically store information in a volatile table without having it written to a real database in a file on the disk.

In that sense multidimensional arrays extend a lot the functionalities of simple arrays, which are oriented to storing just series of polymorphic values such as:
  • Strings: ‘Apples’, ‘Peaches’, ‘Oranges’, ‘Apricots’
  • Integers: 1,2,3,4,5
  • ... or a mix of any kind of value: ‘Sergio’,’Leone’,’January’,3,1929,’Rome’

In fact you can imagine a multidimensional array like a table, with columns and rows, where each cell has its own index (i.e. [7,5]).

To make things clear, let’s set-up a multidimensional array and let’s see how it can be used, for instance, to manage our employees coming and going.

First let’s do some cleaning on our screen and let’s define a $emp_counter variable which we can later use to retrieve information about employees:
  1. clear-host  
  2. write-host "==========STARTED=============="  
  3. $emp_counter = $null # Empty key to track our employees  
Ok, the game commences here. We define here the desired array as a dynamic array. We go for the following syntax:
  1. $employee_list = @() # Dynamic array definition  
This will result in an empty array named $employee_list. The drawback of an empty array such this is that each time you will perform an operation on it, such as adding a row, or just adding a single value, Windows Powershell will have to rebuild (by making a copy) the whole array to follow its structural modifications. And of course this would have a high impact on the performance of your script if thousands or even millions of operations are performed. Nonetheless an array with no predefined size will give us a greater flexibility. That’s why we call it a dynamic array in the end, or a jagged array to be more precise.

We could also have start by defining a fixed size array (which is called a true multidimensional array) but in our example we don’t want to stick to a square matrix so we will not use the following command (but you are free to do so if you want to deeper explore arrays in Powershell):
  1. $employee_list = New-Object 'object[,]' 4,5 # Alternative method to create arrays  
Now let’s check how Powershell sees this array object:
  1. write-host "==============================="  
  2. write-host "Checking array information"  
  3. write-host "==============================="  
  4. $employee_list.gettype() # Check array information  
The output is:
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
Now let’s add four rows, one for each of our new IT administrators: Tommy, Jonathan, Mario and Jeremy.
  1. $emp_counter ++ # We increase the counter by one  
  2. $employee_list += ,@($emp_counter'Tommy',76000, 'Sysadm','Windows')  
  3. $emp_counter ++  
  4. $employee_list += ,@($emp_counter'Jonathan',80000, 'Sysadm','Unix'#  
  5. $emp_counter ++  
  6. $employee_list += ,@($emp_counter'Mario',64000,'DBA','Oracle'#  
  7. $emp_counter ++  
  8. $employee_list += ,@($emp_counter'Jeremy',70000, 'DBA','MS SQL'#  
Now let’s run through the array to see its content row by row:
  1. write-host "==============================="  
  2. write-host "Array filled with four rows"  
  3. write-host "==============================="  
  4. foreach($employee in $employee_list)  
  5.     {  
  6.     Write-host ($employee# List array content using foreach()  
  7.     }  
Here’s the output:
1 Tommy 76000 Sysadm Windows
2 Jonathan 80000 Sysadm Unix
3 Mario 64000 DBA Oracle
4 Jeremy 70000 DBA MS SQL
Each time we put a comma, we are like telling Powershell to start a new row in the multidimensional array. Here’s a graphical representation:


As you can see, for the moment this jagged array resembles a square matrix with four rows and five columns. The first column is our subjective index, the second is the name, the third is the salary, the fourth is the role, and the fifth is the specialization.
This was the easy part. Now let’s have a look at the different operations that can be performed on this scaring polymorphic multidimensional array. In particular, let’s see how to:
  • Add a row to the jagged array
  • Add a row with a non standard number of values
  • Delete a row from the jagged array
  • Sort the content of the array by one known column
Doing these operations will show you the power of Powershell and of its data types!!!
Let’s start with adding a full grown-up row for our new networking specialist, Bill:
  1. $employee_list += ,@($emp_counter,'Bill',81000,'Network','Cisco')  
Here’s what we have now:
  1. write-host "==============================="  
  2. write-host "Bill is hired"  
  3. write-host "==============================="  
  4. foreach($employee in $employee_list)  
  5.     {  
  6.     Write-host ($employee)  
  7.     }  
The output is:
1 Tommy 76000 Sysadm Windows
2 Jonathan 80000 Sysadm Unix
3 Mario 64000 DBA Oracle
4 Jeremy 70000 DBA MS SQL
5 Bill 81000 Network Cisco
Or graphically:


Now let’s see how to add a row with a non standard number of values. For instance, our new employee Cedric has skills in both Windows and NAS worlds, so we will have six columns instead of five like for the others employees. This will change our array from a true multidimensional array to a jagged array (for more details on the difference between jagged arrays and true multidimensional arrays check this cool post.)

We would add Cedric to our array this way:
  1. $employee_list += ,@($emp_counter,'Cedric',75000,'Sysadm','Windows','Netapp')  
We get:
  1. write-host "==============================="  
  2. write-host "Cedric Is hired"  
  3. write-host "==============================="  
  4. foreach($employee in $employee_list)  
  5.     {  
  6.     Write-host ($employee)  
  7.     }  
The output will be:
1 Tommy 76000 Sysadm Windows
2 Jonathan 80000 Sysadm Unix
3 Mario 64000 DBA Oracle
4 Jeremy 70000 DBA MS SQL
5 Bill 81000 Network Cisco
6 Cedric 75000 Sysadm Windows Netapp
Or to be more clear:


As you can see, the array has extended to include one more column and its form is now irregular.

Now let’s suppose that our firm is facing an economical crisis and we, the good managers, want to reduce our costs and keep earning more $$$. We could fire Tommy because Cedric is more convenient due to the fact that he has two competences: Windows and NAS. So, let’s have a look at how to delete the row for Tommy from our multidimensional array. As far as I know, no nice easy way to do this. We must go through a foreach() cycle and then overwrite the original array with all the rows except Tommy’s one, which we will have found and excluded using the classical -notmatch operator against field [1] :
  1. $employee_list_temp = @()  
  2. Foreach($employee2 in $employee_list)  
  3.     {  
  4.     If($employee2[1] –notmatch "Tommy"# Index 1 is for the employee’s name  
  5.         {  
  6.         $employee_list_temp += ,($employee2# ... bye-bye Tommy  
  7.         }  
  8.     }   
  9. $employee_list = $employee_list_temp # Updating array content   
  10. write-host "==============================="  
  11. write-host "Tommy has left… ;-)"  
  12. write-host "==============================="  
  13. foreach($employee in $employee_list)  
  14.     {  
  15.     Write-host ($employee)  
  16.     }  
We get:
2 Jonathan 80000 Sysadm Unix
3 Mario 64000 DBA Oracle
4 Jeremy 70000 DBA MS SQL
5 Bill 81000 Network Cisco
6 Cedric 75000 Sysadm Windows Netapp
As you can see the row for Tommy has magically disappeared. The array first row is Jonathan’s one now.

The last action we want to be able to perform is to sort the content of out multidimensional array. To do so, nothing better than the classical sort-object cmdlet using field number 2, which is the salary in ascending order in our case:
  1. $employee_list_by_wage = $employee_list | sort-object @{Expression={$_[2]}; Ascending=$true}  
The information is stored in the object $employee_list_by_wage. We can now cycle in $employee_list_by_wage to get the list of our employees sorted by wage:
  1. write-host "==============================="  
  2. write-host "Employees by salary"  
  3. write-host "==============================="  
  4. foreach($employee3 in $employee_list_by_wage)  
  5.     {  
  6.     Write-host ($employee3)  
  7.     }  
  8. write-host "==========DONE================"  
The output will look like this:
3 Mario 64000 DBA Oracle
4 Jeremy 70000 DBA MS SQL
6 Cedric 75000 Sysadm Windows Netapp
2 Jonathan 80000 Sysadm Unix
5 Bill 81000 Network Cisco
Here’s the graphical version:


Ok guys, now you know the basics of multidimensional array handling in Powershell and you know how to add and remove elements from jagged arrays. Of course this script can be optimized, reorganized, compacted, but this is out of my scope. I just wanted to quickly demonstrate the ease with which Powershell manages data. I hope that you see my point of view. Powershellers, if you want to suggest any improvements or correction, you are warmly welcome to do so!

Here's the full code that you can run at once:
  1. clear-host  
  2. write-host "==========STARTED=============="  
  3. $emp_counter = $null # Empty key to track our employees  
  4. $employee_list = @() # Dynamic array definition  
  5. write-host "==============================="  
  6. write-host "Checking array information"  
  7. write-host "==============================="  
  8. $employee_list.gettype() # Check array information  
  9. $emp_counter ++ # We increase the counter by one  
  10. $employee_list += ,@($emp_counter'Tommy',76000, 'Sysadm','Windows')  
  11. $emp_counter ++  
  12. $employee_list += ,@($emp_counter'Jonathan',80000, 'Sysadm','Unix')  
  13. $emp_counter ++  
  14. $employee_list += ,@($emp_counter'Mario',64000,'DBA','Oracle')  
  15. $emp_counter ++  
  16. $employee_list += ,@($emp_counter'Jeremy',70000, 'DBA','MS SQL')  
  17. write-host "==============================="  
  18. write-host "Array filled with four rows"  
  19. write-host "==============================="  
  20. foreach($employee in $employee_list)  
  21.     {  
  22.     Write-host ($employee# List array content using foreach()  
  23.     }  
  24. $emp_counter ++ # We increase the counter by one  
  25. $employee_list += ,@($emp_counter,'Bill',81000,'Network','Cisco')  
  26. write-host "==============================="  
  27. write-host "Bill is hired"  
  28. write-host "==============================="  
  29. foreach($employee in $employee_list)  
  30.     {  
  31.     Write-host ($employee)  
  32.     }  
  33. $emp_counter ++ # We increase the counter by one  
  34. $employee_list += ,@($emp_counter,'Cedric',75000,'Sysadm','Windows','Netapp')  
  35. write-host "==============================="  
  36. write-host "Cedric Is hired"  
  37. write-host "==============================="  
  38. foreach($employee in $employee_list)  
  39.     {  
  40.     Write-host ($employee)  
  41.     }  
  42. $employee_list_temp = @()  
  43. Foreach($employee2 in $employee_list)  
  44.     {  
  45.     If($employee2[1] –notmatch "Tommy"# Index 1 is for the employee’s name  
  46.         {  
  47.         $employee_list_temp += ,($employee2# ... bye-bye Tommy  
  48.         }  
  49.     }   
  50. $employee_list = $employee_list_temp # Updating array content   
  51. write-host "==============================="  
  52. write-host "Tommy has left… ;-)"  
  53. write-host "==============================="  
  54. foreach($employee in $employee_list)  
  55.     {  
  56.     Write-host ($employee)  
  57.     }  
  58. $employee_list_by_wage = $employee_list | sort-object @{Expression={$_[2]}; Ascending=$true}  
  59. write-host "==============================="  
  60. write-host "Employees by salary"  
  61. write-host "==============================="  
  62. foreach($employee3 in $employee_list_by_wage)  
  63.     {  
  64.     Write-host ($employee3)  
  65.     }  
  66. write-host "==========DONE================"  
Please feel free to tell me if you have found this article useful!!!

16 comments:

  1. Thanks for the post, was looking for something clean on multidimensional arrays.

    You just forgot to increment emp_counter before adding Bill and Cendric. They both showed up as "4" like Jeremy.

    ReplyDelete
  2. This popped up at the top of my Google search and deservedly so. *Very* well done. Thanks so much for a clear and concise summary of the subject.

    ReplyDelete
  3. Very good post, thank you. It's very useful.

    ReplyDelete
  4. Thank you for that post.

    ReplyDelete
  5. Nice post. We end up with 3 people who have the emp_counter set a 4 because you didn't continue to increase it each time, but very very helpful indeed.

    ReplyDelete
  6. Thanks for the hint! I corrected the final code by adding

    $emp_counter ++ # We increase the counter by one

    when we add Bill and Cedric.
    Thanks fro the feedback

    ReplyDelete
  7. Excellent: easy and clear.

    :)

    ReplyDelete
  8. Great example with clear explanation

    ReplyDelete
  9. Excellent; Solved my problems...

    ReplyDelete
  10. How do you select a column from that table and display just 1 column? Say you want to just see a list of employee names.
    Since there's no column name, i'm having a little trouble with that.

    ReplyDelete
  11. Great and clear article, very helpful.

    ReplyDelete
  12. Clear explanation! Very helpful. Thank you

    ReplyDelete
  13. Thanks for this clear explanation. Great work!

    ReplyDelete
  14. Great article but could do with knowing how to change a value in an array, i.e. if someone was to get a payrise how would you change the values

    ReplyDelete
  15. This really helped me. Thank you so much!

    ReplyDelete

Related Posts Plugin for WordPress, Blogger...