'Find common items in two PowerShell arrays
I know there are a couple stackoverflow questions on this already but I have tried many solutions and keep getting the same error. The problem is this:
I have 2 arrays (each index represents a folder)
$originDirsArr = @(2, 257, 256, 3, 4, 10)
$tempDirArr = @(2, 257, 256, 3, 4)
I want to compare $arr2 against $arr1 and if there is something in $arr1 that is not in $arr2, then remove it. Ie, in this situation 10 DNE in $arr2, so that folder should be deleted.
This is what I have tried:
$c = Compare-Object -ReferenceObject ($originDirsArr) `
-DifferenceObject ($tempDirArr) -Passthru
$c
Also:
while ($t -lt $originDirsArr.length){
$originDirsArr[$t]
if ( $tempDirArr -notContain $originDirsArr[$t]){
"$_ does not exist in original and needs to be deleted"
}else{
"$_ does still exist in the temp"
}
$t++
}
And finally:
Compare-Object $originDirsArr $tempDirArr | ForEach-Object { $_.InputObject }
Each time I keep getting some sort of error either being the ReferenceObject or the DifferenceObject is null. I know that it is not null because I can print out the contents and even when indexed on t in that one example, I still have the contents.
Solution 1:[1]
I also have a bit of a distaste for compare-object. Since these are simple arrays, a single foreach loop and -notcontains will do the trick.
$originDirsArr = @(2, 257, 256, 3, 4, 10)
$tempDirArr = @(2, 257, 256, 3, 4)
foreach ($item in $originDirsArr) {
if ($tempDirArr -notcontains $item) {
Write-Output "Do something with $item";
}
}
Solution 2:[2]
Use the HashSet generic collection. It requires .NET 3.5. It has an IntersectWith method which will modify the set to include only items in both collections (i.e. like ANDing two collections):
$originDirsSet = New-Object 'Collections.Generic.HashSet[int]' ,@(2, 257, 256, 3, 4, 10)
$tempDirSet = @(2, 257, 256, 3, 4)
$originDirsSet.IntersectWith( $tempDirSet )
# $originalDirsSet now contains 2, 257, 256, 3, 4
It has other set-based methods:
ExceptWith: Removes all elements in the specified collection from the currentHashSet<T>object.SymmetricExceptWith: Modifies the currentHashSet<T>object to contain only elements that are present either in that object or in the specified collection, but not both (i.e. this is likeXORing the collections).UnionWith: Modifies the currentHashSet<T>object to contain all elements that are present in itself, the specified collection, or both (i.e. this is likeORing the collections).
Solution 3:[3]
Here's a 1 liner to do this. It's the same as the accepted answer in that it iterates through one array then uses -contains to compare that item with the other array. The difference here is we're using a where-object function and returning the result set, rather than having an if statement within a for loop. Also making use of PowerShell's shorthand notation to reduce the amount of code (whether that's good or "bad-clever" is debatable).
#setting up
$originDirsArr = @(2, 257, 256, 3, 4, 10)
$tempDirArr = @(2, 257, 256, 229, 3, 4)
#get items in 1 array but not the other
[int[]]$leftOnly = $originDirsArr | ?{$tempDirArr -notcontains $_}
#or an intersect operation
[int[]]$intersect = $originDirsArr | ?{$tempDirArr -contains $_}
#display results
"Left Only"; $leftOnly
"Intersect"; $intersect
Or if this is something you're doing often, it may be useful to have a convenient function for this type of operation:
set-alias ?@@ Apply-ArrayOperation #http://stackoverflow.com/a/29758367/361842
function Apply-ArrayOperation {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position=0)]
[object[]]$Left
,
[Parameter(Mandatory,ParameterSetName='exclude', Position=1)]
[switch]$exclude
,
[Parameter(Mandatory,ParameterSetName='intersect', Position=1)]
[switch]$intersect
,
[Parameter(Mandatory,ParameterSetName='outersect', Position=1)] #not sure what the correct term for this is
[switch]$outersect
,
[Parameter(Mandatory,ParameterSetName='union', Position=1)] #not sure what the correct term for this is
[switch]$union
,
[Parameter(Mandatory,ParameterSetName='unionAll', Position=1)] #not sure what the correct term for this is
[switch]$unionAll
,
[Parameter(Mandatory, Position=2)]
[object[]]$Right
)
begin {
#doing this way so we can use a switch staement below, whilst having [switch] syntax for the function's caller
[int]$action = 1*$exclude.IsPresent + 2*$intersect.IsPresent + 3*$outersect.IsPresent + 4*$union.IsPresent + 5*$unionAll.IsPresent
}
process {
switch($action) {
1 {$Left | ?{$Right -notcontains $_}}
2 {$Left | ?{$Right -contains $_} }
3 {@($Left | ?{$Right -notcontains $_}) + @($Right | ?{$Left -notcontains $_})}
4 {@($Left) + @($Right) | select -Unique}
5 {@($Left) + @($Right)}
}
}
}
$array1 = @(1,3,5,7,9)
$array2 = @(2,3,4,5,6,7)
"Array 1"; $array1
"Array 1"; $array2
"Array 1 Exclude Array 2"; ?@@ $array1 -exclude $array2
"Array 1 Intersect Array 2"; ?@@ $array1 -intersect $array2
"Array 1 Outersect Array 2"; ?@@ $array1 -outersect $array2
"Array 1 Union Array 2"; ?@@ $array1 -union $array2
"Array 1 UnionAll Array 2"; ?@@ $array1 -unionall $array2
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | alroc |
| Solution 2 | Aaron Jensen |
| Solution 3 |
