74 votes

Comparaison de deux tableaux et obtention des valeurs non communes

Je voulais une petite logique pour comparer le contenu de deux tableaux et obtenir la valeur qui n'est pas commune entre eux en utilisant powershell.

exemple si

$a1=@(1,2,3,4,5)
$b1=@(1,2,3,4,5,6)

$c qui est la sortie devrait me donner la valeur " 6 "qui est la sortie de ce qui est la valeur non commune entre les deux tableaux.

Quelqu'un peut-il m'aider à le faire ? Merci !

108voto

Shay Levy Points 41404
PS > $c = Compare-Object -ReferenceObject (1..5) -DifferenceObject (1..6) -PassThru
PS > $c
6

80voto

iRon Points 41

Collection

$a = 1..5
$b = 4..8

$Yellow = $a | Where {$b -NotContains $_}

$Yellow contient tous les éléments de $a sauf ceux qui sont dans $b :

PS C:\> $Yellow
1
2
3

$Blue = $b | Where {$a -NotContains $_}

$Blue contient tous les éléments de $b sauf ceux qui sont dans $a :

PS C:\> $Blue
6
7
8

$Green = $a | Where {$b -Contains $_}

Pas en question, mais quand même ; Green contient les éléments qui sont dans les deux $a y $b .

PS C:\> $Green
4
5

<em>Note </em>: <code>Where</code> est un alias de <code>Where-Object</code> . Les alias peuvent introduire des problèmes éventuels et rendre les scripts difficiles à maintenir.


Addendum 12 octobre 2019

Comme commenté par @xtreampb et @mklement0 : bien que non montré par l'exemple de la question, la tâche que la question implique (valeurs "pas en commun") est la différence symétrique entre les deux ensembles d'entrée (l'union de jaune et bleu) .

Union

La différence symétrique entre les $a y $b peut être littéralement définie comme l'union de $Yellow y $Blue :

$NotGreen = $Yellow + $Blue

Ce qui est écrit :

$NotGreen = ($a | Where {$b -NotContains $_}) + ($b | Where {$a -NotContains $_})

Performance

Comme vous pouvez le remarquer, cette syntaxe comporte un certain nombre de boucles (redondantes) : all items in list $a itérer (en utilisant Where ) à travers les éléments de la liste $b (en utilisant -NotContains ) et vice-versa. Malheureusement, la redondance est difficile à éviter car il est difficile de prédire le résultat de chaque côté. Une table de hachage est généralement une bonne solution pour améliorer les performances des boucles redondantes. Pour cela, j'aime redéfinir la question : Obtenez les valeurs qui apparaissent une fois dans la somme des collections ( $a + $b ) :

$Count = @{}
$a + $b | ForEach-Object {$Count[$_] += 1}
$Count.Keys | Where-Object {$Count[$_] -eq 1}

En utilisant le ForEach au lieu de l'instruction ForEach-Object et le Where au lieu de la méthode Where-Object vous pourriez augmenter les performances d'un facteur 2,5 :

$Count = @{}
ForEach ($Item in $a + $b) {$Count[$Item] += 1}
$Count.Keys.Where({$Count[$_] -eq 1})

LINQ

Mais Requête intégrée au langage (LINQ) battra facilement toutes les méthodes natives de PowerShell et de .Net (voir aussi PowerShell haute performance avec LINQ et la réponse de mklement0 pour La boucle foreach imbriquée suivante peut-elle être simplifiée en PowerShell ? :

Pour utiliser LINQ, vous devez définir explicitement les types de tableaux :

[Int[]]$a = 1..5
[Int[]]$b = 4..8

Et utilisez le [Linq.Enumerable]:: opérateur :

$Yellow   = [Int[]][Linq.Enumerable]::Except($a, $b)
$Blue     = [Int[]][Linq.Enumerable]::Except($b, $a)
$Green    = [Int[]][Linq.Enumerable]::Intersect($a, $b)
$NotGreen = [Int[]]([Linq.Enumerable]::Except($a, $b) + [Linq.Enumerable]::Except($b, $a))

Point de repère

Les résultats des tests de référence dépendent fortement de la taille des collections et du nombre d'éléments réellement partagés. En moyenne, je suppose que la moitié de chaque collection est partagée avec l'autre.

Using             Time
Compare-Object    111,9712
NotContains       197,3792
ForEach-Object    82,8324
ForEach Statement 36,5721
LINQ              22,7091

Pour obtenir une bonne comparaison des performances, les caches doivent être vidés, par exemple en démarrant une nouvelle session PowerShell.

$a = 1..1000
$b = 500..1500

(Measure-Command {
    Compare-Object -ReferenceObject $a -DifferenceObject $b  -PassThru
}).TotalMilliseconds
(Measure-Command {
    ($a | Where {$b -NotContains $_}), ($b | Where {$a -NotContains $_})
}).TotalMilliseconds
(Measure-Command {
    $Count = @{}
    $a + $b | ForEach-Object {$Count[$_] += 1}
    $Count.Keys | Where-Object {$Count[$_] -eq 1}
}).TotalMilliseconds

(Measure-Command {
    $Count = @{}
    ForEach ($Item in $a + $b) {$Count[$Item] += 1}
    $Count.Keys.Where({$Count[$_] -eq 1})
}).TotalMilliseconds

[Int[]]$a = $a
[Int[]]$b = $b
(Measure-Command {
    [Int[]]([Linq.Enumerable]::Except($a, $b) + [Linq.Enumerable]::Except($b, $a))
}).TotalMilliseconds

15voto

stej Points 14257

Regardez Compare-Object

Compare-Object $a1 $b1 | ForEach-Object { $_.InputObject }

Ou si vous souhaitez savoir à quoi appartient l'objet, regardez SideIndicator :

$a1=@(1,2,3,4,5,8)
$b1=@(1,2,3,4,5,6)
Compare-Object $a1 $b1

3voto

Essayez :

$a1=@(1,2,3,4,5)
$b1=@(1,2,3,4,5,6)
(Compare-Object $a1 $b1).InputObject

Ou, vous pouvez utiliser :

(Compare-Object $b1 $a1).InputObject

L'ordre n'a pas d'importance.

3voto

user2096392 Points 141

Vos résultats ne seront utiles que si les tableaux sont d'abord triés. Pour trier un tableau, passez-le dans Sort-Object.

$x = @(5,1,4,2,3)
$y = @(2,4,6,1,3,5)

Compare-Object -ReferenceObject ($x | Sort-Object) -DifferenceObject ($y | Sort-Object)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X