Rob Traynere
Rob Traynere

Reputation: 704

finding index of key in an ordered dictionary in powershell

I am having a little bit of trouble with hashtables/dictionaries in powershell. The most recent roadblock is the ability to find the index of a key in an ordered dictionary.

Consider the following example:

$dictionary = [Ordered]@{
    'a' = 'blue';
    'b'='green';
    'c'='red'
    }

If this were a normal array I'd be able to look up the index of an entry by using IndexOf().

[array]::IndexOf($dictionary,'c'). 

That would return 2 under normal circumstances.

If I try that with an ordered dictionary, though, I get -1.

Any solutions?


Edit: In case anyone reading over this is wondering what I'm talking about. What I was trying to use this for was to create an object to normalize property entries in a way that also has a numerical order.

I was trying to use this for the status of a process, for example:

$_processState = [Ordered]@{
     'error' = 'error'
     'none' = 'none'
     'started' = 'started'
     'paused' = 'paused'
     'cleanup' = 'cleanup'
     'complete' = 'complete'
}

If you were able to easily do this, the above object would give $_processState.error an index value of 0 and ascend through each entry, finally giving $_processState.complete an index value of 5. Then if you compared two properties, by "index value", you could see which one is further along by simple operators. For instance:

$thisObject.Status = $_processState.complete
If ($thisObject.Status -ge $_processState.cleanup) {Write-Host 'All done!'}

PS > All done!

^^that doesn't work as is, but that's the idea. It's what I was aiming for. Or maybe to find something like $_processState.complete.IndexNumber()

Having an object like this also lets you assign values by the index name, itself, while standardizing the options...

$thisObject.Status = $_processState.paused
$thisObject.Status

PS > paused

Not really sure this was the best approach at the time or if it still is the best approach with all the custom class options there are available in PS v5.

Upvotes: 4

Views: 13818

Answers (3)

Rob Traynere
Rob Traynere

Reputation: 704

For the initial problem I was attempting to solve, a comparable process state, you can now use Enumerations starting with PowerShell v5.

You use the Enum keyword, set the Enumerators by name, and give them an integer value. The value can be anything, but I'm using ascending values starting with 0 in this example:

Enum _ProcessState{
    Error = 0
    None = 1
    Started = 2
    Paused = 3
    Cleanup = 4
    Complete = 5
    Verified = 6
}

#the leading _ for the Enum is just cosmetic & not required

Once you've created the Enum, you can assign it to variables. The contents of the variable will return the text name of the Enum, and you can compare them as if they were integers.

$Item1_State = [_ProcessState]::Started
$Item2_State = [_ProcessState]::Cleanup

#return state of second variable
$Item2_state

#comparison
$Item1_State -gt $Item2_State

Will return:

Cleanup
False

If you wanted to compare and return the highest:

#sort the two objects, then return the first result (should return the item with the largest enum int)
$results = ($Item1_State,$Item2_State | Sort-Object -Descending)
$results[0]

Fun fact, you can also use arithmetic on them, for example:

$Item1_State + 1
$Item1_State + $Item2_State

Will return:

Paused
Verified

More info on Enum here:

Upvotes: 1

Code Jockey
Code Jockey

Reputation: 6721

It can be simpler

It may not be any more efficient than the answer from Frode F., but perhaps more concise (inline) would be simply putting the hash table's keys collection in a sub expression ($()) then calling indexOf on the result.

For your hash table...

Your particular expression would be simply:

$($dictionary.keys).indexOf('c')

...which gives the value 2 as you expected. This also works just as well on a regular hashtable... unless the hashtable is modified in pretty much any way, of course... so it's probably not very useful in that case.

In other words

Using this hash table (which also shows many of the ways to encode 4...):

$hashtable = [ordered]@{
    sample = 'hash table'
    0 = 'hello'
    1 = 'goodbye'
    [char]'4' = 'the ansi character 4 (code 52)'
    [char]4 = 'the ansi character code 4'
    [int]4 = 'the integer 4'
    '4' = 'a string containing only the character 4'
    5 = "nothing of importance"
}

would yield the following expression/results pairs:

# Expression                                 Result
#-------------------------------------      -------------
$($hashtable.keys).indexof('5')              -1
$($hashtable.keys).indexof(5)                 7
$($hashtable.keys).indexof('4')               6
$($hashtable.keys).indexof([char]4)           4
$($hashtable.keys).indexof([int]4)            5
$($hashtable.keys).indexof([char]'4')         3
$($hashtable.keys).indexof([int][char]'4')   -1
$($hashtable.keys).indexof('sample')          0

by the way:

  • [int][char]'4' equals [int]52
  • [char]'4' has a "value" (magnitude?) of 52, but is a character, so it's used as such

...gotta love the typing system, which, while flexible, can get really really bad at times, if you're not careful.

Upvotes: 8

Frode F.
Frode F.

Reputation: 54921

Dictionaries uses keys and not indexes. OrderedDictionary combines a hashtable and ArrayList to give you order/index-support in a dictionary, however it's still a dictionary (key-based) collection.

If you need to get the index of an object in a OrderedDictionary (or a hasthable) you need to use foreach-loop and a counter. Example (should be created as a function):

$hashTable = [Ordered]@{
    'a' = 'blue';
    'b'='green';
    'c'='red'
}

$i = 0
foreach($key in $hashTable.Keys) {
    if($key -eq "c") { $i; break }
    else { $i++ }
}

That's how it works internaly too. You can verify this by reading the source code for OrderedDictionary's IndexOfKey method in .NET Reference Source

Upvotes: 2

Related Questions