Array.Find and IndexOf for multiple elements that are exactly the same object
Solution 1
You can do this:
$b = "A","D","B","D","C","E","D","F"
(0..($b.Count-1)) | where {$b[$_] -eq 'D'}
1
3
6
Solution 2
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param($i) $arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param($arr, $val)
$i = 0
foreach ($el in $arr) { if ($el -eq $val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls
[array]::IndexOf()
in a loop instead of enumerating all elements.Use of a script block (invoked with call operator
&
and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable$i
.The
foreach
statement is faster than theForeach-Object
cmdlet (whose built-in aliases are%
and, confusingly, alsoforeach
).-
Simply (implicitly) outputting
$i
for each match makes PowerShell collect multiple results in an array.- If only one index is found, you'll get a scalar
[int]
instance instead; wrap the whole command in@(...)
to ensure that you always get an array.
- If only one index is found, you'll get a scalar
While
$i
by itself outputs the value of$i
,++$i
by design does NOT (though you could use(++$i)
to achieve that, if needed).Unlike
Array.IndexOf()
, PowerShell's-eq
operator is case-insensitive by default; for case-sensitivity, use-ceq
instead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach ($el in $Array) {
if ($el -eq $Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Solution 3
You would still need to loop with the static methods from [array]
but if you are still curious something like this would work.
$b = "A","D","B","D","C","E","D","F"
$results = @()
$singleIndex = -1
Do{
$singleIndex = [array]::IndexOf($b,"D",$singleIndex + 1)
If($singleIndex -ge 0){$results += $singleIndex}
}While($singleIndex -ge 0)
$results
1
3
6
Loop until a match is not found. Assume the match at first by assigning the $singleIndex
to -1 ( Which is what a non match would return). When a match is found add the index to a results array.
ALIENQuake
Updated on July 18, 2022Comments
-
ALIENQuake almost 2 years
I have trouble of getting index of the current element for multiple elements that are exactly the same object:
$b = "A","D","B","D","C","E","D","F" $b | ? { $_ -contains "D" }
Alternative version:
$b = "A","D","B","D","C","E","D","F" [Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" })
This will return: D D D
But this code:
$b | % { $b.IndexOf("D") }
Alternative version:
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" }) | % { $b.IndexOf($_) }
Returns:
1 1 1
so it's pointing at the index of the first element. How to get indexes of the other elements?