Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 671
ALIENQuake Avatar asked Dec 29 '14 14:12

ALIENQuake


People also ask

How do you use indexOf with array of objects?

To find the index of an object in an array, by a specific property: Use the map() method to iterate over the array, returning only the value of the relevant property. Call the indexOf() method on the returned from map array. The indexOf method returns the index of the first occurrence of a value in an array.

How do you find the index of all occurrences of an element in an array?

To get the index of all occurrences of an element in a JavaScript array: Create an empty array, that will store the indexes. Use a for loop and loop from 0 up to the array's length. For each iteration of the loop, check if the element at that index is equal to the specific element.

What is the difference between findIndex and indexOf in JavaScript?

findIndex - Returns the index of the first element in the array where predicate is true, and -1 otherwise. indexOf - Returns the index of the first occurrence of a value in an array.

How do you remove an object from an array?

Combining indexOf() and splice() Methods Pass the value of the element you wish to remove from your array into the indexOf() method to return the index of the element that matches that value in the array. Then make use of the splice() method to remove the element at the returned index.


2 Answers

You can do this:

$b = "A","D","B","D","C","E","D","F" 

(0..($b.Count-1)) | where {$b[$_] -eq 'D'}

1
3
6
like image 99
mjolinor Avatar answered Oct 06 '22 19:10

mjolinor


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 the Foreach-Object cmdlet (whose built-in aliases are % and, confusingly, also foreach).

  • 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.
  • 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
like image 38
mklement0 Avatar answered Oct 06 '22 21:10

mklement0