web-dev-qa-db-fra.com

Différence de deux tableaux à l'aide de Perl

J'ai deux tableaux. Je dois vérifier et voir si les éléments de l'un apparaissent dans l'autre.

Existe-t-il un moyen plus efficace de le faire que les boucles imbriquées? J'ai quelques milliers d'éléments dans chacun et j'ai besoin d'exécuter le programme fréquemment.

34
Buzkie

Une autre façon de le faire est d'utiliser Array :: Utils

use Array::Utils qw(:all);

my @a = qw( a b c d );
my @b = qw( c d e f );

# symmetric difference
my @diff = array_diff(@a, @b);

# intersection
my @isect = intersect(@a, @b);

# unique union
my @unique = unique(@a, @b);

# check if arrays contain same members
if ( !array_diff(@a, @b) ) {
        # do something
}

# get items from array @a that are not in array @b
my @minus = array_minus( @a, @b );
38
Nifle

perlfaq4 à la rescousse:

Comment calculer la différence de deux tableaux? Comment calculer l'intersection de deux tableaux?

Utilisez un hachage. Voici le code pour faire les deux et plus. Il suppose que chaque élément est unique dans un tableau donné:

   @union = @intersection = @difference = ();
    %count = ();
    foreach $element (@array1, @array2) { $count{$element}++ }
    foreach $element (keys %count) {
            Push @union, $element;
            Push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
    }

Si vous déclarez correctement vos variables, le code ressemble plus à ce qui suit:

my %count;
for my $element (@array1, @array2) { $count{$element}++ }

my ( @union, @intersection, @difference );
for my $element (keys %count) {
    Push @union, $element;
    Push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
}
25
mob

Vous devez fournir beaucoup plus de contexte. Il existe des moyens plus efficaces de le faire, allant de:

  • Sortez de Perl et utilisez Shell (sort + comm)

  • map un tableau dans un hachage Perl puis boucle sur l'autre en vérifiant l'appartenance au hachage. Cela a une complexité linéaire ("M + N" - essentiellement une boucle sur chaque tableau une fois) par opposition à une boucle imbriquée qui a une complexité "M * N")

    Exemple:

    my %second = map {$_=>1} @second;
    my @only_in_first = grep { !$second{$_} } @first; 
    # use a foreach loop with `last` instead of "grep" 
    # if you only want yes/no answer instead of full list
    
  • Utilisez un module Perl qui fait le dernier point pour vous (List :: Compare a été mentionné dans les commentaires)

  • Faites-le en fonction des horodatages de la date à laquelle les éléments ont été ajoutés si le volume est très important et que vous devez souvent comparer à nouveau. Quelques milliers d'éléments ne sont pas vraiment assez gros, mais j'ai récemment dû différencier des listes de taille 100k.

11
DVK

Tu peux essayer Arrays::Utils, et cela donne une apparence agréable et simple, mais il ne fait aucune magie puissante sur le back-end. Ici se trouve le array_diffs code:

sub array_diff(\@\@) {
    my %e = map { $_ => undef } @{$_[1]};
    return @{[ ( grep { (exists $e{$_}) ? ( delete $e{$_} ) : ( 1 ) } @{ $_[0] } ), keys %e ] };
}

Puisque Arrays::Utils n'est pas un module standard, vous devez vous demander si cela vaut la peine d'installer et de maintenir ce module. Sinon, c'est assez proche de la réponse de DVK .

Il y a certaines choses auxquelles vous devez faire attention, et vous devez définir ce que vous voulez faire dans ce cas particulier. Disons:

@array1 = qw(1 1 2 2 3 3 4 4 5 5);
@array2 = qw(1 2 3 4 5);

Ces tableaux sont-ils les mêmes? Ou sont-ils différents? Ils ont les mêmes valeurs, mais il y a des doublons dans @array1 et pas @array2.

Et ça?

@array1 = qw( 1 1 2 3 4 5 );
@array2 = qw( 1 1 2 3 4 5 );

Je dirais que ces tableaux sont les mêmes, mais Array::Utils::arrays_diff supplie de différer. Ceci est dû au fait Array::Utils suppose qu'il n'y a pas d'entrées en double.

Et, même le Perl FAQ signalé par mob dit également que Il suppose que chaque élément est unique dans un tableau donné . Est-ce une hypothèse que vous pouvez faire?

Quoi qu'il en soit, les hachages sont la réponse. Il est facile et rapide de rechercher un hachage. Le problème est de savoir ce que vous voulez faire avec des valeurs uniques.

Voici une solution solide qui suppose que les doublons n'ont pas d'importance:

sub array_diff {
    my @array1 = @{ shift() };
    my @array2 = @{ shift() }; 

    my %array1_hash;
    my %array2_hash;

    # Create a hash entry for each element in @array1
    for my $element ( @array1 ) {
       $array1_hash{$element} = @array1;
    }

    # Same for @array2: This time, use map instead of a loop
    map { $array_2{$_} = 1 } @array2;

    for my $entry ( @array2 ) {
        if ( not $array1_hash{$entry} ) {
            return 1;  #Entry in @array2 but not @array1: Differ
        }
    }
    if ( keys %array_hash1 != keys %array_hash2 ) {
       return 1;   #Arrays differ
    }
    else {
       return 0;   #Arrays contain the same elements
    }
}

Si les doublons comptent, vous aurez besoin d'un moyen de les compter. Voici comment utiliser map non seulement pour créer un hachage avec la clé de chaque élément du tableau, mais aussi pour compter les doublons dans le tableau:

my %array1_hash;
my %array2_hash;
map { $array1_hash{$_} += 1 } @array1;
map { $array2_hash{$_} += 2 } @array2;

Maintenant, vous pouvez parcourir chaque hachage et vérifier que non seulement les clés existent, mais que leurs entrées correspondent

for my $key ( keys %array1_hash ) {
    if ( not exists $array2_hash{$key} 
       or $array1_hash{$key} != $array2_hash{$key} ) {
       return 1;   #Arrays differ
    }
 }

Vous ne quitterez la boucle for que si toutes les entrées de %array1_hash correspond à leurs entrées correspondantes dans %array2_hash. Maintenant, vous devez montrer que toutes les entrées de %array2_hash correspondent également à leurs entrées dans %array1_hash, et cela %array2_hash n'a pas plus d'entrées. Heureusement, nous pouvons faire ce que nous avons fait auparavant:

if ( keys %array2_hash != keys %array1_hash ) {
     return 1;  #Arrays have a different number of keys: Don't match
}
else {
     return;    #Arrays have the same keys: They do match
}
7
David W.

Vous pouvez l'utiliser pour obtenir la différence entre deux tableaux

#!/usr/bin/Perl -w
use strict;

my @list1 = (1, 2, 3, 4, 5);
my @list2 = (2, 3, 4);

my %diff;

@diff{ @list1 } = undef;
delete @diff{ @list2 };
3
Pradeep Gupta
my @a = (1,2,3); 
my @b=(2,3,1); 
print "Equal" if grep { $_ ~~ @b } @a == @b;
1
rlib

algorithme n + n log n, si sûr que les éléments sont uniques dans chaque tableau (en tant que clés de hachage)

my %count = (); 
foreach my $element (@array1, @array2) { 
    $count{$element}++;
}
my @difference = grep { $count{$_} == 1 } keys %count;
my @intersect  = grep { $count{$_} == 2 } keys %count;
my @union      = keys %count;

Donc, si je ne suis pas sûr de l'unité et que je veux vérifier la présence des éléments de array1 à l'intérieur de array2,

my %count = (); 
foreach (@array1) {
    $count{$_} = 1 ;
};
foreach (@array2) {
    $count{$_} = 2 if $count{$_};
};
# N log N
if (grep { $_ == 1 } values %count) {
    return 'Some element of array1 does not appears in array2'
} else {
    return 'All elements of array1 are in array2'.
} 
# N + N log N
1
MUY Belgium

Pas élégant, mais facile à comprendre:

#!/usr/local/bin/Perl 
use strict;
my $file1 = shift or die("need file1");
my $file2 = shift or die("need file2");;
my @file1lines = split/\n/,`cat $file1`;
my @file2lines = split/\n/,`cat $file2`;
my %lines;
foreach my $file1line(@file1lines){
    $lines{$file1line}+=1;
}
foreach my $file2line(@file2lines){
    $lines{$file2line}+=2;
}
while(my($key,$value)=each%lines){
    if($value == 1){
        print "$key is in only $file1\n";
    }elsif($value == 2){
        print "$key is in only $file2\n";
    }elsif($value == 3){
        print "$key is in both $file1 and $file2\n";
    }
}
exit;
__END__
0
skiddy1454576

Vous voulez comparer chaque élément de @x avec l'élément du même index dans @y, non? Cela suffira.

print "Index: $_ => \@x: $x[$_], \@y: $y[$_]\n" 
    for grep { $x[$_] != $y[$_] } 0 .. $#x;

...ou...

foreach( 0 .. $#x ) {
    print "Index: $_ => \@x: $x[$_], \@y: $y[$_]\n" if $x[$_] != $y[$_];
}

Le type que vous choisissez dépend de si vous êtes plus intéressé à conserver une liste d'indices pour les éléments différents, ou simplement intéressé à traiter les asymétries une par une. La version grep est pratique pour obtenir la liste des discordances. ( message d'origine )

0
Eugen Konkov

Essayez d'utiliser List: Compare. Le service informatique propose des solutions pour toutes les opérations pouvant être effectuées sur des baies. https://metacpan.org/pod/List::Compare

0
Newbie