Array.Find and IndexOf for multiple elements that are exactly the same object

I am having trouble getting the index of the current item for multiple items 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($_) } 

Return:

1 1 1

so that it points to the index of the first element. How to get indices of other elements?

+5
source share
3 answers

You can do it:

 $b = "A","D","B","D","C","E","D","F" (0..($b.Count-1)) | where {$b[$_] -eq 'D'} 1 3 6 
+9
source

mjolinor's answer is conceptually elegant , but slow with large arrays , presumably because of the need to create a parallel array of indexes first (which is also memory inefficient).

It is conceptually similar to the following LINQ (PSv3 +) based solution , 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' } ) 

Although any PowerShell looping solution is ultimately slow compared to the compiled language, the next 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 this unexpected solution is even faster than the Matt> solution, which calls [array]::IndexOf() in a loop instead of listing all the elements.

  • The use of a script block (called with the & operator and arguments), although not strictly necessary, is used to prevent infection of the surrounding area using the $i helper variable.

  • The foreach is faster than the Foreach-Object (whose built-in aliases are % and, vaguely, also foreach ).

  • The simple (implicit) output of $i for each match causes PowerShell to collect multiple results in an array.

    • If only one index is found, instead you get a scalar [int] ; complete the whole command in @(...) to make sure you always get an array.
  • While $i itself infers the value of $i , ++$i by design is NOT (although you could use (++$i) to achieve this if necessary).

  • Unlike Array.IndexOf() , the PowerShell -eq is not -eq by default; for case sensitivity, use -ceq .


It is easy to turn the above into a (simple) function (note that the parameters are set non-standard, 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 
+3
source

You still need a loop with static methods from [array] , but if you're still interested, something like this will 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 found. Assume first a match by assigning $singleIndex -1 (this is what returned not a match). When a match is found, add the index to the result array.

+1
source

Source: https://habr.com/ru/post/1210054/