Comparing pairs of arrays in Powershell - arrays

I've got multiple pairs of Arrays.
The first of the pair contains multiple strings.
The second contains a timestamp belonging to the string at the same position of the first array.
Here are two pairs of arrays for an example:
#String array
M681
T9997
E61
H717
K700
#Timestamp Array
11:00
11:05
11:05
11:10
11:15
The second pair of Arrays would look like this:
#String array
B722
T732
L999
M681
I125
#Timestamp Array
11:00
11:00
11:05
11:10
11:15
I want to find matches where a string is in two pairs and then i want to measure the time difference between both entries.
Comparing the string-arrays was simply done by (Compare-Object -IncludeEqual -ReferenceObject $a_string -DifferenceObject $b_string -ExcludeDifferent).InputObject, but with this i couldnt compare the responding timestamps.
So my next idea was forging each pair together in a hashtable:
$hash = #{}
$hash.Add($a_string[$i],$a_timestamp[$i])
But i noticed that this would not work in my enviroment since both the strings and the timestamps could reoccur in one array, so setting one of them as the key of the hash would not be possible.
Next I tried creating a hashtable with an array as value and an index as key:
$hash.Add($indexNumber,#($a_string[$i],$a_timestamp[$i]))
Even though $hash.indexNumber prints out both values, i cant seem to get on of the values by $hash.indexNumber[0] / $hash.indexNumber[1]
I would appreciate if someone could tell me the best practice with this kind of situation or could tell me how I can successfully get the seperate Values of the array inside the hash and compare them to other array values in different hashes.
Kind regards,
Paxz.

So this is a relatively naive solution and will probably need some work to properly handle edge cases, but hopefully it will get you going in the right direction:
$arrayNames1 = #('M681','T9997','E61','H717','K700')
$arrayTimes1 = #('11:00','11:05','11:05','11:10','11:15')
$arrayNames2 = #('B722','T732','L999','M681','I125')
$arrayTimes2 = #('11:00','11:00','11:05','11:10','11:15')
function Find-AllIndexesOf{
param(
$array,
$term
)
$i = 0
foreach($item in $array){
if($item -eq $term){
$i
}
$i++
}
}
foreach($name in $arrayNames1){
if($arrayNames2.Contains($name)){
$array1Locations = Find-AllIndexesOf $arrayNames1 $name
$array2Locations = Find-AllIndexesOf $arrayNames2 $name
$i = 0
foreach($location in $array1Locations){
$thisTime1 = [datetime]$arrayTimes1[$array1Locations]
$thisTime2 = [datetime]$arrayTimes2[$array2Locations[$i]]
Write-Host ('I found a time difference for {0} that started at {1} and ended at {2} for {3} total minutes' -f $name, $thisTime1, $thisTime2, ($thisTime2 - $thisTime1).TotalMinutes)
$i++
}
}
}

But i noticed that this would not work in my enviroment since both the strings and the timestamps could reoccur in one array, so setting one of them as the key of the hash would not be possible.
I am unclear on why a simple hashtable does not work in your use case.
$table = #{}
for ($i = 0; $i -lt $a_string.count; $i++){
if(!$table.Contains($a_string[$i])){
$table.Add($a_string[$i], #())
$table[$a_string[$i]] += $a_timestamp[$i]
}else{
$table[$a_string[$i]] += $a_timestamp[$i]
}
}
This would generate a table mapped with Strings to times which could then be used to compare against other pairs.

Related

How to combine every element in an array with every element in multiple other arrays in Powershell

In Powershell, I am trying to combine the elements of several arrays to create an array of unique strings. I need to combine every element from each array with every element in the other arrays. It is difficult to concisely explain what I mean, so it may be easier to show.
I start with a 2d-array that looks something like this:
$array = #('this', ('A','B','C'), ('1','2','3','4'), 'that')
I need to create an array, whose contents will look like this:
thisA1that
thisA2that
thisA3that
thisA4that
thisB1that
thisB2that
thisB3that
thisB4that
thisC1that
thisC2that
thisC3that
thisC4that
The length and number of arrays in the original array are variable, and I don't know the order of the items in the original array.
So far, I've tried a few methods, but my logic has been wrong. Here was my first attempt at a solution:
$tempList = #()
#get total number of resources that will be created
$total = 1
for($i=0; $i -lt $array.Count; $i++){$total = $total * $array[$i].Count}
# build each resource from permutations of array parts
for ($i = 0; $i -lt $array.Count; $i++)
{
for ($j = 0; $j -lt $total; $j++)
{
$tempList += #('')
$idx = $total % $array[$i].Count
# item may either be string or an array. If string, just add it. If array, add the item at the index
if ($array[$i] -is [array]){ $tempList[$j] += $array[$i][$idx] }
else { $tempList[$j] += $array[$i] }
}
}
In this example, my logic with the modulus operator was wrong, so it would grab the only the first index of each array every time. Upon further consideration, even if I fix the modulus logic, the overall logic would still be wrong. For example, if the second two arrays were the same size, I would get 'A' paired with '1' each time, 'B' with '2', etc.
I'm sure there is a way to do this, but I simply can't seem to see it.
I think the answer's to use recursion so you can handle the fact that the array of arrays can be any length. This recursive function should:
take the first array from the array-of-arrays
loop through each item in that first array
if the first array is also the last array, just return each item.
if there are more arrays in the array-of-arrays then pass the remaining arrays to the recursive function
for each result from the recursive function, prefix the return value with the current item from the first array.
I think the code explains itself better than the above:
function Combine-Array {
[CmdletBinding()]
Param (
[string[][]]$Array
)
Process {
$current = $Array[0]
foreach ($item in $current) {
if ($Array.Count -gt 1) {
Combine-Array ([string[][]]#($Array | Select -Skip 1)) | %{'{0}{1}' -f $item, $_}
} else {
$item
}
}
}
}
Combine-Array #('this', ('A','B','C'), ('1','2','3','4'), 'that')
You can write pipeline function, which would add one more subarray into the mix. And then you call it as many times as many subarrays you have:
function CartesianProduct {
param(
[array] $a
)
filter f {
$i = $_
$a[$MyInvocation.PipelinePosition - 1] | ForEach-Object { $i + $_ }
}
Invoke-Expression ('''''' + ' | f' * $a.Length)
}
CartesianProduct ('this', ('A','B','C'), ('1','2','3','4'), 'that')

PowerShell - Create an array that ignores duplicate values

Curious if there a construct in PowerShell that does this?
I know you can do this:
$arr = #(1,1,1,1,2,2,2,2,3,3,3,3,3,4,4,4,4,4)
$arr = $arr | Get-Unique
But seems like performance-wise it would be better to ignore the value as you are entering it into the array instead of filtering out after the fact.
If are you inserting a large number of items in to an array (thousands) the performance does drop, because the array needs to be reinitialized every time you add to it so it may be better in your case, performance wise, to use something else.
Dictionary, or HashTable could be a way. Your single dimensional unique array could be retrieved with $hash.Keys For example:
$hash = ${}
$hash.Set_Item(1,1)
$hash.Set_Item(2,1)
$hash.Set_Item(1,1)
$hash.Keys
1
2
If you use Set_Item, the key will be created or updated but never duplicated. Put anything else for the value if you're not using it, But maybe you'll have a need for a value with your problem too.
You could also use an Arraylist:
Measure-Command -Expression {
$bigarray = $null
$bigarray = [System.Collections.ArrayList]#()
$bigarray = (1,1,1,1,2,2,2,2,3,3,3,3,3,4,4,4,4,4)
$bigarray | select -Unique
}
Time passed:
TotalSeconds : 0,0006581
TotalMilliseconds : 0,6581
Measure-Command -Expression {
$array = #(1,1,1,1,2,2,2,2,3,3,3,3,3,4,4,4,4,4)
$array | select -Unique
}
Time passed:
TotalSeconds : 0,0009261
TotalMilliseconds : 0,9261

How to index into PSObject Array of Hashtables

I'm working in powershell with an imported CSV. I have a data set like this:
ID First_Name Last_Nam
-- ---------- --------
2314 Kenny Anderson
21588 Mark Anderson
2547 Ben Andrews
5797 Benjamin Armour
Except with 2000 people and many more columns. Currently the data is stored as a series of hashes in a #{ID = "..",First_Name:"..",Last_Name:".."} and these are stored in a System Object array. I want to store each hash as an index in an array. I want to get the hashtable at that index, but I don't know how to into the System.Object Array. Here's my code:
$csv = import-csv $csv_name
$row = #(0)*csv.length
$hash = #{}
for($i =0; $i -lt $csv.length; $i++){
$row[$i] += $csv[$i]
}
#error: cannot convert "#{ID : "..", First_Name: "..", Last_Name:".." to Systems.Int32
for($i =0; $i -lt $csv.length; $i++){
$csv[$i].psobject.properties | Foreach { $hash[$_.Name] = $_.Value }
$row[$i]+=$hash
}
#error: Cannot convert systems.collections.hashtable into Systems.Int32
I'm looking for a way to index the array so I can get the hashtable at that index. the The first one, with pointers to the hashtables accessible through the array, is what I think would be the easiest to manipulate. If there's an easier way to get a specific hashtable just from the System.Object[] itself, please tell me.
I should add I don't know the names or amount of the columns in advance.
The return value of Import-Csv is not an array of [hashtable]s, it's an array of [psobject]s, where each column is a property.
There's no need to build any kind of array to get an individual object by index.
$csv = import-csv $csv_name
$csv[0]
$csv[1].First_Name
# etc
The errors in your code have nothing to do with the question you posed though; you're getting errors trying to add objects to an array, because you're actually trying to add an object or hashtable to an existing array element of type integer.
You don't need to precreate the array with a bunch of zeros, so this line isn't needed:
$row = #(0)*csv.length
Instead, you can create an empty array:
$row = #()
Then, you can just add to the array itself:
$row += $hash

Unique Combos from powershell array - No duplicate combos

I'm trying to figure out the best way to get unique combinations from a powershell array. For instance, my array might be
#(B,C,D,E)
I would be hoping for an output like this :
B
C
D
E
B,C
B,D
B,E
C,D
C,E
D,E
B,C,D
C,D,E
B,C,D,E
I do not want re-arranged combos. If combo C,D exists already then I do not want combo D,C. It's redundant for my purposes.
I looked into the functions here : Get all combinations of an array
But they aren't what I want. I've been working on figuring this out myself, but have spent quite a bit of time without success. I thought I'd ask the question here so that if someone else already know I'm not wasting my time.
Thanks!
This is an adaptation from a solution for a C# class I took that asked this same question. For any set find all subsets, including the empty set.
function Get-Subsets ($a){
#uncomment following to ensure only unique inputs are parsed
#e.g. 'B','C','D','E','E' would become 'B','C','D','E'
#$a = $a | Select-Object -Unique
#create an array to store output
$l = #()
#for any set of length n the maximum number of subsets is 2^n
for ($i = 0; $i -lt [Math]::Pow(2,$a.Length); $i++)
{
#temporary array to hold output
[string[]]$out = New-Object string[] $a.length
#iterate through each element
for ($j = 0; $j -lt $a.Length; $j++)
{
#start at the end of the array take elements, work your way towards the front
if (($i -band (1 -shl ($a.Length - $j - 1))) -ne 0)
{
#store the subset in a temp array
$out[$j] = $a[$j]
}
}
#stick subset into an array
$l += -join $out
}
#group the subsets by length, iterate through them and sort
$l | Group-Object -Property Length | %{$_.Group | sort}
}
Use like so:
PS C:>Get-Subsets #('b','c','d','e')
b
c
d
e
bc
bd
be
cd
ce
de
bcd
bce
bde
cde
bcde
Note that computational costs go up exponentially with the length of the input array.
Elements SecondstoComplete
15 46.3488228
14 13.4836299
13 3.6316713
12 1.2542701
11 0.4472637
10 0.1942997
9 0.0867832
My tired attempt at this. I did manage to get it to produce the expected results but how it does it is not as elegant. Uses a recursive functionality.
Function Get-Permutations{
Param(
$theInput
)
$theInput | ForEach-Object{
$element = $_
$sansElement = ($theInput | Where-Object{$_ -ne $element})
If($sansElement.Count -gt 1){
# Build a collection of permutations using the remaining elements that were not isolated in this pass.
# Use the single element since it is a valid permutation
$perms = ,$element
For($elementIndex = 0;$elementIndex -le ($sansElement.Count - 1);$elementIndex++){
$perms += ,#(,$element + $sansElement[0..$elementIndex] | sort-object)
}
# For loop does not send to output properly so that is the purpose of collecting the results of this pass in $perms
$perms
# If there are more than 2 elements in $sansElement then we need to be sure they are accounted for
If($sansElement -gt 2){Get-Permutations $sansElement}
}
}
}
Get-Permutations B,C,D,E | %{$_ -join ","} | Sort-Object -Unique
I hope I can explain myself clearly....So each pass of the function will take an array. Each individual element of that array will be isolated from the rest of the array which is represented by the variables $element and $sansElement.
Using those variables we build individual and progressively larger arrays composing of those elements. Let this example show using the array 1,2,3,4
1
1,2
1,2,3
1,2,3,4
The above is done for each "number"
2
2,1
2,1,3
2,1,3,4
and so forth. If the returned array contains more that two elements (1,2 would be the same as 2,1 in your example so we don't care about pairs beyond one match) we would take that array and run it through the same function.
The real issue is that the logic here (I know this might be hard to swallow) creates several duplicates. I suppose you could create a hashtable instead which I will explore but it does not remove the logic flaw.
Regardless of me beating myself up as long as you don't have thousands of elements the process would still produce results.
Get-Permutations would return and array of arrays. PowerShell would display that one element per line. You asked for comma delimited output which is where -join comes in. Sort-Object -Unique takes those sorted string an discards the duplicates.
Sample Output
B
B,C
B,C,D
B,C,D,E
B,C,E #< Missing from your example output.
B,D
B,D,E #< Missing from your example output.
B,E
C
C,D
C,D,E
C,E
D
E

create a new variable for each loop in a foreach-loop

How can I put $org into an array together with $count?
Like this example array:
$myArray = #{
1="SampleOrg";
2="AnotherSampleOrg"
}
Another example:
$myArray = #{
$count="$org";
$count="$org"
}
Example foreach:
$count=0;get-organization | foreach {$count++; $org = $_.Name.ToString();write-host $count -nonewline;write-host " $org"}
$answer = read-host "Select 1-$count"
The above will display:
1 SampleOrg
2 AnotherSampleOrg
Select 1-2:
What I would like to do afterwards is to put the array to use in a switch.
Example:
switch ($answer)
{
1 {$org=myArray[1]} #<-- or whatever that corresponds to "SampleOrg"
2 {$org=myArray[2]} #<-- or whatever that corresponds to "AnotherSampleOrg"
}
You have to initialize your hashtable somewhere before the loop:
$myArray = #{}
and add a
$myArray.Add($count, $org)
to your foreach-loop.
EDIT: For the discussion about hastable/array see the whole thread ;) I just kept the name of the variable from the original posting
Looks like you're confusing arrays and Hashtables. Arrays are ordered, and indexed by an numeric value. Hashtables are associative, and indexed by any value that has equality defined.
This is array syntax
$arr = #(1,2,3)
and this is Hashtable syntax
$ht = #{red=1;blue=2;}
For your question, the following will work
$orgs = #(get-organization | % { $_.Name })
this will create a 0 based array, mapping int -> OrgName, so
$orgs[$answer]
will get the correct name. Or if you're using 1 based indexing
$orgs[$answer-1]
Note, I removed the switch, as there's no reason for it.

Resources