Get key name in one member hashtable - powershell

I have a data structure for a multi-level lookup table that looks something like this
$lookupTable = #{
'a' = #{
'a1' = 'A One'
'a2' = 'A Two'
'a3' = 'A Three'
}
'b' = #{
'b1' = 'B One'
'b2' = 'B Two'
'b3' = 'B Three'
}
}
I use a data structure like this to pass an arbitrary number of new and revised values to update the table, like this.
$hash=#{'a'=#{'a4'='A Four'}
'b'=#{'b4'='B Four'}
'c'=#{'c1'='C One'
'c2'='C Two'
}
}
I can then use nested foreach loops to get the key names to add or update.
However, I also need to extract just a single value, and I want to keep a similar data structure so I can pass just one hash table to specify exactly what value I am looking for, such as $getValue = #{'b'='b1'}.
But to do that I need to get the key name at the first(only) index. I have tried
Write-Host "$($getValue.GetEnumerator[0])"
Write-Host "$($getValue.keys.GetEnumerator[0])"
Write-Host "$($getValue.GetEnumerator[1])"
Write-Host "$($getValue.keys.GetEnumerator[1])"
and none work. I could use a foreach again, which will only look once, but that seems inelegant. So, what am I doing wrong?
EDIT: Just to verify the loop based approach works, I did this
foreach ($key in $getValue.keys) {
if ($key) {
Write-Host "$key $($getValue.$key) = $($lookupTable.$key.($getValue.$key))"
}
break
}
and it does indeed come back with b b1 = B one. but what an ugly way to do it.
Dang, getting closer!
$key = $getValue.keys[0] does provide a key of b. But $value = $getValue.$key doesn't return b1 as expected. However, $value = $getValue[$key] does! however, then trying
$key = $getValue.keys[0]
$value = $getValue[$key]
Write-Host "$key $($getValue[$key]) = $($lookupTable.$key.$value)"
doesn't come back with the actual final value. Grr. More coffee and try again.
AHA! $getValue.keys[0] doesn't return a string. But if you cast to a string then everything is dandy. So
$key = [string]$getValue.keys[0]
$value = $getValue.$key
Write-Host "$key $value = $($lookupTable.$key.$value)"
works a treat. So I answered my own question. I guess I'll let the mods decide if this might be educational for others, or should just be deleted.

I think you can simplify this by declaring the $getValue not as a Hashtable, but rather as a simple array:
# define the value to get as array of keys.
# Element 0 => the key for $lookupTable,
# Element 1 => the key for the first nested hash
$getValue = 'b','b1'
# now, simply get the value you are looking for like this:
Write-Host $lookupTable[$getvalue[0]][$getValue[1]]
# or like so:
Write-Host $lookupTable.$($getvalue[0]).$($getvalue[1])
# or by using helper variables:
$lookupKey, $nestedKey = $getValue
Write-Host $lookupTable.$lookupKey.$nestedKey
All of the above will return the value B One

Related

Powershell: Passing a `Read-Host` as the key to HashTable

I am trying to pass user input to hashTable, which I am attempting to do.
$someHash =
#{
1 = "one";
2 = "two";
3 = "three"
}
$x = Read-Host "enter 1,2,3"
Write-Output($someHash.$x)
For some reason $someHash.$x and $someHash[$x] return null, but $x has it value.
I also can get values out of the map when I hard code keys. Not sure what I am doing incorrectly.
Adding to address comments.
I have also tried casting with the same result.
$someHash.[int]$x
$someHash[[int]$x]
[int]$x = Read-Host "enter 1,2,3"
I have also used strings as keys using single and double-quotes. Same outcome.
Using string keys you won't have a need to convert the result of Read-Host to int.
Note, if below doesn't work, it's most likely because $x was type constrained before ([int]$x), use Remove-Variable x or restart your PowerShell session.
$someHash =
#{
'1' = "one";
'2' = "two";
'3' = "three"
}
$x = Read-Host "enter 1,2,3"
$someHash[$x] # => Works (Recommended)
$someHash.$x # => Works
Using int keys you would need to type constrain the result of Read-Host to int:
$someHash =
#{
1 = "one";
2 = "two";
3 = "three"
}
[int]$x = Read-Host "enter 1,2,3"
$someHash[$x] # => Works (Recommended)
$someHash.$x # => Works

Convert all values of OrderedDictionary of the powershell to string line and print to the log file

I have some problems with converting values of the OrderedDictionary of the PowerShell to the string line. I have the following hash table [OrderedDictionary]:
I try outputting data of values to a string:
for ($i = 0; $i -lt $DataResult.Count; $i++) {
$DataResult["$i"].Values |
ForEach-Object {
Write-Output $_
}
}
But it doesn't work, could you help me
Given your code sample I think you have an array of [Ordered] dictionaries? If so you should be able to unroll the values quite easily:
#This is just demo data for my testing:
$Dictionaries = #(
[Ordered]#{
P1 = 'Something'
P2 = 'SomethingElse'
}
[Ordered]#{
P1 = 'Another'
P2 = 'AnotherAnother'
}
)
# Unroll the values:
$Values = $Dictionaries.Values
If you ignore the demo data it's really just 1 line. $Values would be of type [Object[]] the elements of which are in their original string type. If needed you can re-cast as a string array:
$Values = [String[]]$values
Or you can specify directly with the unrolling:
# Unroll the values:
$Values = [String[]]$Dictionaries.Values
Or Type constrain the variable:
# Unroll the values:
[String[]]$Values = $Dictionaries.Values
Note: This casting will convert element values to strings as well. I'm going on the basis that's desired.
I'd also point out you don't really need Write-Output anywhere. Firstly, that's already implicit in normal PowerShell operations. Secondly, you cannot pipe a traditional For loop (although you can assign its output to a variable). At any rate, if your intent is to simply continue feeding this down the pipeline, you could remove $Values from any of the above examples and PowerShell will implicitly and natively do just that.

Processing items within a powershell array - multiple items

I have been reading about arrays in powershell and hash tables, I know the basic workings of an array and how to use foreach loop to get items within the array, my challange here is slightly different. I would like to pay what I call a multi dimension array to a script, and process the items contained within the array.
What is my setup.
$x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
$k = 'serverid','servername','locationid','appid' # key names correspond to data positions in each array in $x
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
What am i trying to achieve ?
I am trying to achieve the structure of a database table within powershell. So literally treating each array item as a row.
So for example
(1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
could be thought of as a table like below
enter image description here
I then want to loop for each item in the array to get the values, similar to the below example
[0].serverid = 1, [0].servername = server1, [0].locationid = 3, [0].applicationID = 1
[1].serverid = 4, [1].servername = server2, [1].locationid = 6, [1].applicationID = 2
what have I done ?
$x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
$k = 'serverid','servername','locationid','appid' # key names correspond to data positions in each array in $x
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
$x
for ($i = 0; $i -lt $x.Count; $i++)
{
$myserverid = $x[$i][0]
$myservername = $x[$i][1]
$mylocationid = $x[$i][2]
$myappid = $x[$i][3]
write-host $myserverid
}
The Issues
If I set the following $x = (1,"Server1",3,1), then the loop is somewhat incorrect which is why I think the approach is wrong (more than one item works i.e $x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)). The loop only works if you have more than one item within the array, hence why I want to re-examine the way the loop works.
Thanks in advance
Your approach relies on a nested (jagged) array: That is, you have an array of subarrays, each of which represents the tuple of values you want to assign to properties.
If there's only one subarray, you must create the nested array explicitly, using the unary form of , the array constructor operator:
# Construct a 1-element array that contains the 4-element subarray.
$x = , (1,"Server1",3,1)
With two or more, subarrays, you implicitly get a nested array:
# Construct a 3-element array, each element of which contains a 4-element subarray.
$x = (1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3)
Note that in PSv5+ you could use a custom class to solve your problem:
class Custom {
[int] $serverid; [string] $servername;[int] $locationid; [int] $appid
Custom($propValueArray) {
$this.serverid = $propValueArray[0]; $this.servername = $propValueArray[1]; $this.locationid = $propValueArray[2]; $this.appid = $propValueArray[3]
}
}
# Use an array cast to construct [Custom] instances.
# Note the need for (...) around the array, because casts have high
# precedence in PowerShell.
[Custom[]] ((1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3))
This would allow for processing such as:
# Construct all objects
$objects = [Custom[]] ((1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3))
# Process each object.
foreach ($object in $objects) {
($myserverid = $object.serverid) # assign a property to a var; `()` also outputs
# ...
}

Adding a hash-table to a hash-table

I'm trying to add a hash-table to a hash-table using powershell. However, Im getting the following error:
Item has already been added. Key in dictionary: 'Dev' Key being added: 'Dev'
Here's my code:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
$Applications += #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
What am I doing wrong?
I think what you want is something more like this:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
$Applications[$i] = #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
I will also point out that Hashtables often need to be handled defensively. Each key must be unique but values do not need to be. Here is the typical method of handling that:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
if($Applications.ContainsKey($i)){
#Do things here if there is already an entry for this key
}else{
$Applications[$i] = #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
}
EBGreen's helpful answer offers a solution for what you likely meant to do.
To complement that with an explanation of why your code failed:
When you use + to "add" two hashtables, their entries are merged: in other words: the entries of the RHS are added to the LHS hashtable.
(Technically, a new instance is created with the merged entries.)
However - by sensible design - merging is only performed if the hashtables have no keys in common; otherwise, you'll get the error message you saw, complaining about duplicate keys.
If this safeguard weren't in place, you would lose data if the values associated with duplicate entries differ.
Since your loop repeatedly tried to merge a hashtable with the same keys directly into an existing hashtable, your 2nd loop iteration invariably failed.
You can verify this more simply:
$Applications = #{} # create empty hashtable.
# Merge a hashtable literal into $Applications.
# This works fine, because the two hashtables have no keys in common.
$Applications += #{ first = 1; second = 2 }
# $Application now contains the following: #{ first = 1; second = 2 }
# If you now try to add a hashtable with the same set of keys again,
# the operation invariably fails due to duplicate keys.
$Applications += #{ first = 10; second = 20 } # FAILS
# By contrast, adding a hashtable with unique keys works fine:
$Applications += #{ third = 3; fourth = 4 } # OK
# $Application now contains: #{ first = 1; second = 2; third = 3; fourth = 4 }
I was looking for a way to do some sort of "list of enum" and since I'm fairly new to PowerShell, the only thing I could think of is "HashTable of HashTables!".
Could not find a way to do it all "inline", and I got some "can't add a null key to a hashtable" error. After some experimentation I ended up with this, working exactly as I wanted :
$Enums = #{}
$Enums.Frequency = #{
Daily = "daily"
Weekly = "weekly"
Monthly = "monthly"
}
$Enums.SomethinElse = #{
Thing1 = "dflskdjf"
Thing2 = 12
Thing3 = $null
}
You can then use it simply
$Enums.SomethinElse.Thing2
or (if you add it to a Global Variable)
$Global:Enums.Frequency.Daily
It's not exactly the same as the example provided but directly in line with the topic, so I hope it helps someone ending up here :)

how to empty an array when the value of the array changes?

lets assume i have an array #atom. i am pushing three elements $a, $b, $c (residue name, chain and residue number respectively fetched from pdb file) into that array. for instance, $b has values AAAAAAAAA, BBBBBBB, CCCCCCC. how to empty the array every time when $b changes?
The array is as follows,
push(#atom,"$a $b $c");
I'm not sure why you are using an array when you're only storing a single value in it. You think you are storing three values, but you are putting those into a single string before storing them in the array.
To store three values in your array, you might use code like this:
my #atom = ($residue_name, $chain, $residue_number);
(Notice that I have also changed your variable names. $a, $b and $c are terrible names for variables and $a and $b are special variables for Perl and should not be used in random code.)
I don't really know what you are doing here, but it seems to me that it might make more sense to store this data in a hash.
my %atom = (
residue_name => $residue_name,
chain => $chain,
residue_number => $residue_number,
);
Of course, that's only a guess as I don't know what you need to do with your data - but an important part of programming is to get your data structures right.
But let's assume for now that you're still using your original array and you want to a) see if the $chain variable has changed its value and b) empty the array at that point. You would need to write code something like this:
my #atom = ($residue_name, $chain, $residue_number);
# Store the current value of $chain
my $original_chain = $chain;
Then, later on, you need to check the value has changed and take appropriate action.
if ($chain ne $original_chain) {
#atom = ();
}
Of course, this is all just the sketchiest of suggestions. I have no idea how your code is structured.
Assuming $a $b $c are read in a loop and pushed in an array
while (...) {
# read $a $b $c
if ($b ne $last_b) {
#atom = () # atom is affected to a new empty array
}
push #atom, ...
$last_b=$b
}