2
votes

Identifier les éléments dans un tableau de hachages qui ne sont pas dans un autre tableau de hachages (perl)

Je suis un programmeur perl novice essayant d'identifier quels éléments sont dans un tableau de hachages mais pas dans un autre. J'essaie de rechercher dans le "nouveau" tableau, en identifiant l'id, le titre et les éléments créés qui n'existent pas dans le "vieux" tableau.

Je crois que je l'ai utilisé avec un ensemble de bases pour ( ), mais j'aimerais le faire plus efficacement. Cela n'est venu qu'après avoir essayé d'utiliser grep () et échoué.

Ces tableaux sont construits à partir d'une base de données en tant que telle:

1) if (($p) = grep ($hash_ref->{id}, @rv_old)) {
2) if ($hash_ref->{id} ~~ @rv_old) {

Les tableaux ressemblent à ceci lors de l'utilisation de Dumper (@rv_old) pour les imprimer:

  my $rv_old_ref        = \@rv_old;
  my $rv_new_ref        = \@rv_new;

  for my $i ( 0 .. $#rv_old) {
    my $match = grep { $rv_new_ref->$_ == $rv_old_ref->$_ } @rv_new;
    push @notseen, $match if !$match;
  }

J'ai essayé d'utiliser grep () en utilisant des références de tableau, mais je ne pense pas comprendre les tableaux, les hachages, et références assez bien pour le faire correctement. Mes tentatives échouées avec grep () sont ci-dessous. J'apprécierais toutes les idées sur la façon de faire cela correctement.

Je crois que le problème avec ceci est que je ne sais pas comment référencer le champ id dans le deuxième tableau de hachages. La plupart des exemples utilisant grep () que j'ai vus consistent simplement à parcourir un tableau entier, comme vous le feriez avec grep (1) normal. Je dois parcourir un tableau, en vérifiant chacune des valeurs du champ id avec le champ id d'un autre tableau.

$VAR1 = {
          'title' => 'Legal Notice',
          'created' => '2004-10-07 00:17:45',
          'id' => 14
        };
$VAR2 = {
          'created' => '2004-11-15 16:04:06',
          'id' => 86096,
          'title' => 'IRC'
        };
$VAR3 = {
          'id' => 16,
          'created' => '2004-10-07 16:15:29',
          'title' => 'About'
        };

J'ai également essayé des variantes du grep () ci-dessus :

use DBI;
use strict;
use Data::Dumper;
use Array::Utils qw(:all);
sub db_connect_new();
sub db_disconnect_new($);
sub db_connect_old();
sub db_disconnect_old($);

my $dbh_old   = db_connect_old();
my $dbh_new   = db_connect_new();

# get complete list of articles on each host first (Joomla! system)
my $sql_old   = "select id,title,created from mos_content;"; 
my $sql_new   = "select id,title,created from xugc_content;";

my $sth_old   = $dbh_old->prepare($sql_old);
my $sth_new   = $dbh_new->prepare($sql_new);

$sth_old->execute();
$sth_new->execute();

my $ref_old;
my $ref_new;

while ($ref_old = $sth_old->fetchrow_hashref()) {
  push @rv_old, $ref_old;
}

while ($ref_new = $sth_new->fetchrow_hashref()) {
  push @rv_new, $ref_new;
}

my @seen = ();
my @notseen = ();
foreach my $i (@rv_old) {
   my $id = $i->{id};
   my $title = $i->{title};
   my $created = $i->{created};
   my $seen = 0;
   foreach my $j (@rv_new) {
      if ($i->{id} == $j->{id}) {
         push @seen, $i;
         $seen = 1;
      }
   }
   if ($seen == 0) {
       print "$i->{id},$i->{title},$i->{state},$i->{catid},$i->{created}\n";
      push @notseen, $i;
   }
}


0 commentaires

3 Réponses :


3
votes

Il existe un certain nombre de bibliothèques qui comparent les tableaux. Cependant, votre comparaison implique des structures de données complexes (les tableaux ont des hashrefs comme éléments) et cela complique au moins l'utilisation de tous les modules que je connais.

Voici donc un moyen de le faire à la main. J'utilise le tableau montré et sa copie avec une valeur modifiée.

use warnings;
use strict;
use feature 'say';

use List::Util qw(none);   # in List::MoreUtils with older Perls
use Data::Dump qw(dd pp);

sub hr_eq {
    my ($e1, $e2) = @_; 
    return 0 if scalar keys %$e1 != scalar keys %$e2;
    foreach my $k1 (keys %$e1) {
       return 0 if !exists($e2->{$k1}) or $e1->{$k1} ne $e2->{$k1};            
    }   
    return 1
}

my @a1 = ( 
    { 'title' => 'Legal Notice', 'created' => '2004-10-07 00:17:45', 'id' => 14 },
    { 'created' => '2004-11-15 16:04:06', 'id' => 86096, 'title' => 'IRC' },  
    { 'id' => 16, 'created' => '2004-10-07 16:15:29', 'title' => 'About' }
);        
my @a2 = ( 
    { 'title' => 'Legal Notice', 'created' => '2004-10-07 00:17:45', 'id' => 14 },
    { 'created' => '2004-11-15 16:xxx:06', 'id' => 86096, 'title' => 'IRC' },  
    { 'id' => 16, 'created' => '2004-10-07 16:15:29', 'title' => 'About' }
);

my @only_in_two = grep { 
    my $e2 = $_; 
    none { hr_eq($e2, $_) } @a1;
} @a2;

dd \@only_in_two;

Cela identifie correctement l'élément dans @ a2 qui n'existe pas dans @ a1 (avec xxx dans l'horodatage).

Notes

  • Ceci trouve quels éléments d'un tableau ne sont pas dans un autre, pas la différence complète entre les tableaux. C'est précisément ce que demande la question.

  • La comparaison repose sur les détails de votre structure de données (hashref); il n'y a pas d'échappatoire à cela, à moins que vous ne souhaitiez accéder à des bibliothèques plus complètes (comme Test::More).

  • Ceci utilise la comparaison de chaînes, ne , même pour les nombres et les horodatages. Voyez s'il est logique que vos données réelles utilisent des comparaisons plus appropriées pour des éléments particuliers.

  • La recherche dans une liste entière pour chaque élément d'une liste est un algorithme O (N * M) . Des solutions d'une telle complexité (quadratique) sont utilisables tant que les données ne sont pas trop volumineuses; cependant, une fois que les données sont suffisamment volumineuses pour que les augmentations de taille aient des effets évidents, elles se décomposent rapidement (ralentissent au point d'être inutiles). Il est temps de se faire une idée de cela dans votre cas.

    Une approche O (N + M) existe ici, utilisant des hachages, illustrés dans la réponse ikegami. C'est beaucoup mieux sur le plan algorithmique, une fois que les données sont suffisamment volumineuses pour être affichées. Cependant, comme votre tableau contient une structure de données complexe (hashrefs), un peu de travail est nécessaire pour trouver un programme fonctionnel, d'autant plus que nous ne connaissons pas les données. Mais si vos données sont importantes, vous voulez sûrement mettre en œuvre cela.


Quelques commentaires sur le filtrage.

La question observe correctement que pour chaque élément d'un tableau, tel qu'il est traité dans grep , tout l'autre tableau doit être vérifié.

Ceci est fait dans le corps de grep en utilisant none de List :: Util . Il renvoie true si le code de son bloc évalue false pour tous les éléments de la liste; ainsi, si "aucun" des éléments ne satisfait ce code. C'est le cœur de l'exigence: un élément ne doit pas être trouvé dans l'autre tableau.

Il faut faire attention avec la valeur par défaut $ _ variable , car il est utilisé à la fois par grep et none .

Dans le bloc de grep $ _ alias l'élément actuellement traité de la liste, comme grep les parcourt un par un; nous le sauvegardons dans une variable nommée ( $ e2 ). Puis none arrive et dans son bloc "prend possession" de $ _ , en lui attribuant des éléments de @ a1 au fur et à mesure qu'il les traite. L'élément courant de @ a2 est également disponible puisque nous l'avons copié dans $e2.

Le test effectué dans none code > est tiré dans un sous-programme, que j'appelle hr_eq pour souligner que c'est spécifiquement pour la comparaison d'égalité des (éléments dans) les hashrefs.

C'est dans ce sous-marin que les détails peuvent être modifiés. Premièrement, au lieu d'utiliser brutalement ne pour les valeurs de chaque clé, vous pouvez ajouter des comparaisons personnalisées pour des clés particulières (les nombres doivent utiliser == , etc.). Ensuite, si vos structures de données changent, c'est ici que vous ajusterez les détails.


8 commentaires

Ouais, il n'y a aucun moyen que je puisse comprendre ce que tu as fait :-( Je vais essayer de comprendre.


@AlexRegan Oh, désolé ... Votre propre code n'est pas loin, alors j'ai pensé que ce serait clair avec une certaine réflexion. Je peux ajouter des explications - quelle partie est opaque?


C'est une approche lente (O (N * M) [fondamentalement O (N ^ 2)]) par rapport à la plus rapide disponible (O (N + M) [fondamentalement, O (N)]).


@ikegami Ouais. J'ai réfléchi (brièvement, j'allais y revenir) et je suis allé chercher quelque chose en rapport avec leurs efforts grep . De plus, l'utilisation de hachages (pour un exemple fonctionnel) ne semblait pas aussi propre que l'ensemble du hachage doit être pris en compte d'une manière ou d'une autre. Nous ne savons même pas si des clés peuvent être considérées comme uniques (à l'exception des doublons de hashrefs). J'ai ajouté une puce à ce sujet. Je vous remercie.


Re " Nous ne savons même pas si des clés peuvent être considérées comme uniques ", l'OP mentionne en fait qu'il souhaite uniquement signaler les valeurs id manquantes, mais l'approche de hachage peut toujours être utilisé avec des clés à plusieurs champs en utilisant quelque chose comme sous-clé {pack '(J / a *) *', @ {$ _ [0]} {@key_fields}} pour générer la clé de hachage .


@ikegami " veut seulement signaler les valeurs id manquantes " - oui, vous avez raison; J'ai en quelque sorte ignoré cela (je suppose que je m'attendais à un problème réel plus complexe?). Mais oui, ce pack est une sorte de chose que j'avais en tête :) Merci, je vais étudier ça :))


J / a * crée une chaîne avec un préfixe de longueur ( J = entier non signé de la taille utilisée par Perl). (J / a *) * en crée un pour chaque autre argument. Ces chaînes avec préfixe de longueur sont concaténées. Ainsi, pack ('(J / a *) *', 'abc', 'def') pourrait créer 03 00 00 00 00 00 00 00 00 61 62 63 03 00 00 00 00 00 00 00 64 65 66


@AlexRegan " pas moyen que je puisse comprendre ce que vous avez fait " --- j'avais ajouté des explications détaillées (au cas où vous ne l'auriez pas remarqué). S'ils ne sont pas assez bons, tu peux me le faire savoir



2
votes

Vous pouvez utiliser grep.

my %old_keys;  ++$old_keys{ $_->{id} } for @old_rows;
my %new_keys;  ++$new_keys{ $_->{id} } for @new_rows;

for my $new_row (@new_rows) {
   say "$new_row->{id} not in old"
      if !$old_keys{$new_row->{id}};
}

for my $old_row (@old_rows) {
   say "$old_row->{id} not in new"
      if !$new_keys{$old_row->{id}};
}

Mais c'est une solution O (N * M), alors qu'il existe une solution O (N + M) qui être beaucoup plus rapide.

for my $new_row (@new_rows) {
   say "$new_row->{id} not in old"
      if !grep { $_->{id} == $new_row->{id} } @old_rows;
}

for my $old_row (@old_rows) {
   say "$old_row->{id} not in new"
      if !grep { $_->{id} == $old_row->{id} } @new_rows;
}

Si les deux connexions de votre base de données sont à la même base de données, cela peut être fait beaucoup plus efficacement dans la base de données elle-même.

  1. Créez une table temporaire avec trois champs, id , old_count ( DEFAULT 0 ) et new_count ( DEFAULT 0 ).
  2. INSÉRER OU METTRE À JOUR de l'ancienne table dans la table temporaire, en incrémentant old_count dans le processus.
  3. INSÉRER OU METTRE À JOUR de la nouvelle table dans la table temporaire, en incrémentant new_count dans le processus.
  4. SELECT les lignes de la table temporaire qui ont 0 pour old_count ou 0 pour new_count .


0 commentaires

1
votes
select id,title,created from mos_content
     LEFT JOIN xugc_content USING(id)
     WHERE xugc_content.id IS NULL;
Gives you the rows that are in mos_content but not in xugc_content.That's even shorter than the Perl code.

0 commentaires