06 · Arrays & Hashtables¶
Creating arrays¶
$fruits = "apple", "banana", "cherry" # comma creates an array
$numbers = @(1, 2, 3, 4, 5) # explicit array syntax
$empty = @() # empty array
$fruits[0] # apple
$fruits[-1] # cherry <- negative indices count from the end
# arrays are typed as System.Object[] by default — mixed types are allowed
$mixed = 1, "two", 3.0, $true
$mixed.GetType().Name # Object[]
# strongly-typed arrays constrain every element
[int[]]$scores = 90, 85, 77
Common array operations¶
$numbers = 1, 2, 3, 4, 5
$numbers.Count # 5
$numbers.Length # 5 <- same as .Count for arrays
$numbers + 6 # returns a NEW array: 1 2 3 4 5 6 (arrays are fixed-size)
$numbers[1..3] # slice: 2 3 4
$numbers[0,2,4] # index list: 1 3 5
# arrays are fixed-size; += actually creates a new array each time (slow for big loops)
$list = @()
$list += "a"
$list += "b"
# fine for small scripts; use a generic List[T] or ArrayList for large/growing collections
Growable collections: ArrayList and List[T]¶
$list = [System.Collections.Generic.List[string]]::new()
$list.Add("a")
$list.Add("b")
$list.Remove("a")
$list # b
# a strongly-typed generic list is the recommended growable collection
$scores = [System.Collections.Generic.List[int]]::new()
$scores.AddRange(@(10, 20, 30))
$scores.Add(40)
$scores.Count # 4
Iterating arrays¶
$fruits = "apple", "banana", "cherry"
foreach ($fruit in $fruits) {
Write-Output "I like $fruit"
}
$fruits | ForEach-Object { Write-Output "Pipeline: $_" }
Hashtables¶
Hashtables are PowerShell's key-value dictionary — similar to a Python dict or a Bash associative array, but a first-class type usable everywhere.
$capitals = @{
France = "Paris"
Japan = "Tokyo"
Germany = "Berlin"
}
$capitals["Japan"] # Tokyo
$capitals.Japan # Tokyo <- dot notation also works for simple keys
# adding, updating, removing
$capitals["Italy"] = "Rome" # add
$capitals["Japan"] = "Kyoto" # update (overwrite)
$capitals.Remove("Germany") # remove
$capitals.ContainsKey("France") # True
$capitals.Keys # France, Japan, Italy
$capitals.Values # Paris, Kyoto, Rome
Iterating a hashtable¶
$capitals = @{ France = "Paris"; Japan = "Tokyo"; Germany = "Berlin" }
foreach ($key in $capitals.Keys) {
Write-Output "$key -> $($capitals[$key])"
}
# or, using GetEnumerator() to get key/value pairs directly
$capitals.GetEnumerator() | ForEach-Object {
Write-Output "$($_.Key) -> $($_.Value)"
}
By default, @{} is an unordered Hashtable — key order isn't guaranteed.
Use [ordered]@{} when insertion order matters:
$ordered = [ordered]@{ First = 1; Second = 2; Third = 3 }
$ordered.Keys # First, Second, Third <- guaranteed insertion order
A practical example: word frequency count¶
$text = "the quick brown fox jumps over the lazy dog the fox runs"
$counts = @{}
foreach ($word in $text -split " ") {
if ($counts.ContainsKey($word)) {
$counts[$word]++
} else {
$counts[$word] = 1
}
}
$counts.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 3
Cheat sheet¶
| Syntax | Meaning |
|---|---|
$a = 1,2,3 |
create an array |
$a[0] / $a[-1] |
index / last element |
$a[1..3] |
slice |
$a.Count |
number of elements |
[System.Collections.Generic.List[T]]::new() |
growable, strongly-typed list |
@{ k = v } |
create a hashtable |
$h["key"] / $h.key |
access a value |
$h.ContainsKey("k") |
check key existence |
$h.GetEnumerator() |
iterate key/value pairs |
[ordered]@{} |
hashtable that preserves insertion order |
How It Actually Works¶
A PowerShell array literal (@(1,2,3) or the comma operator's implicit
array-building) creates a fixed-size System.Object[] under the hood —
this is why "appending" with $arr += $x is not appending at all: it
allocates a brand-new array one element larger, copies every existing
element into it, and rebinds $arr to the new array. For an
N-iteration loop doing +=, that's O(N²) total copying, which is the
actual mechanical reason the docs steer you toward [System.Collections.
Generic.List[T]] or an output-collecting pipeline instead — List[T]
uses amortized-doubling internal storage so .Add() is O(1) amortized.
Hashtables (@{}) are PowerShell-literal syntax for
System.Collections.Hashtable, a classic open-addressing/bucket hash
table keyed by GetHashCode()/Equals() — by default case-insensitive
for string keys because PowerShell's hashtable literal uses an
IEqualityComparer (StringComparer.OrdinalIgnoreCase)-backed comparer
rather than .NET's normal case-sensitive default, matching PowerShell's
general case-insensitivity elsewhere. [ordered]@{} swaps the backing
type to System.Collections.Specialized.OrderedDictionary, which
maintains an internal parallel array tracking insertion order alongside
the hash buckets — enumeration order is a property of that extra
structure, not an accident of bucket layout the way plain Hashtable
enumeration order (implementation-defined, and not guaranteed stable
across .NET versions) can appear to be.
Iterating a hashtable with foreach ($kv in $table) yields
DictionaryEntry structs (a Key/Value pair struct), while
$table.GetEnumerator() explicitly requests the same enumerator the
foreach statement uses implicitly — this is why $table.Keys | ForEach-
Object { $table[$_] } and foreach ($kv in $table) { $kv.Value } produce
the same values through two different enumeration paths (one walks keys
and re-indexes, the other walks entries directly).
🔀 See this in another language¶
Exercise¶
Write inventory.ps1 that builds a hashtable mapping item names to
quantities (e.g. apples = 12, bananas = 7, oranges = 20), prints each
item and its quantity sorted by quantity descending using
GetEnumerator() | Sort-Object, and prints the total quantity across all
items using Measure-Object -Sum on the hashtable's values.