How do I do foreach loop with a where clause for a multidimensional array in Powershell?

Viewed 50

For example

$people = @(
 @('adam', '24', 'M')
 @('andy', '20', 'M')
 @('alex', '30', 'M')
 @('ava', '25', 'F')
)

foreach($person in $people | where ($person[1] -lt '25') -and ($person[2] -eq 'M'))

and this should select adam and andy...

3 Answers

The syntax you should use for your Where-Object statement would be:

$people = @(
    @('adam', '24', 'M'),
    @('andy', '20', 'M'),
    @('alex', '30', 'M'),
    @('ava', '25', 'F')
)

$people | Where-Object { $_[1] -lt '25' -and $_[2] -eq 'M' } | ForEach-Object { $_[0] }

# Results in:
#
# adam
# andy

Or using a traditional foreach loop with an if statement:

foreach($array in $people) {
    if($array[1] -lt 25 -and $array[2] -eq 'M') {
        $array[0]
    }
}

However as recommended in previous answer, a hash table might be more suitable for this (even though the syntax is a bit more complicated):

$people = @{
    M = @{
        24 = 'adam'
        20 = 'andy'
        30 = 'alex'
    }
    F = @{
        25 = 'ava'
    }
}

$people['M'][$people['M'].Keys -lt 25]

You can use the answer involving ForEach-Object of course. The only possible issue with this (depending on what you are trying to do with that data) is that a ForEach-Object will execute each record of data individually.

Another way is to create the object you want specifically then use a standard foreach().

$people = @(
 @('adam', '24', 'M')
 @('andy', '20', 'M')
 @('alex', '30', 'M')
 @('ava', '25', 'F')
)

$filteredPeople = $people | Where-Object { $_[1] -lt '25' -and $_[2] -eq 'M'}

foreach($person in $$filteredPeople) {

#stuff

}

This will do the same functionality on the whole object.

Santiago's helpful answer solves your problem well.

Let me offer an alternative that uses a PowerShell v5+ custom class to model the instances of your array, which makes for more descriptive, and type-safe, access to each person's properties:

# Define a [Person] class with a constructor that fills all properties.
class Person {
  [string] $Name
  [int]    $Age
  [char]   $Sex
  Person([string] $name, [int] $age, [char] $sex) { 
    $this.Name = $name; $this.Age = $age; $this.Sex = $sex
  }
}

# Create an array of [Person] instances.
$people = @(
 [Person]::new('adam', '24', 'M')
 [Person]::new('andy', '20', 'M')
 [Person]::new('alex', '30', 'M')
 [Person]::new('ava', '25', 'F')
)

# Filter the array via the .Where() array method.
$people.Where({ $_.Age -lt 25 -and $_.Sex -eq 'M' })

Output:

Name Age Sex
---- --- ---
adam  24   M
andy  20   M

If you were interested in, say, only the .Name property values, simply append .Name to the last command above, which returns array 'adam', 'andy', courtesy of PowerShell's member-access enumeration feature.

Note the use of the .Where() array method, which - for collections already in memory - is a more efficient alternative to the Where-Object cmdlet; that said, a foreach statement performs best.

Related