Идентификация элементов в одном массиве хэшей, которых нет в другом массиве хэшей (perl) - PullRequest
2 голосов
/ 09 марта 2019

Я начинающий программист на Perl, пытающийся определить, какие элементы находятся в одном массиве хэшей, а не в другом.Я пытаюсь выполнить поиск по «новому» массиву, идентифицируя идентификатор, заголовок и созданные элементы, которые не существуют из «старого» массива.

Я полагаю, что он работает с наборомBasic for (), но я бы хотел сделать это более эффективно.Это произошло только после попытки использовать grep () и не удалось.

Эти массивы построены из базы данных следующим образом:

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;
   }
}

Массивы выглядят так при использовании Dumper (@rv_old)) чтобы напечатать их:

$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'
        };

Я пытался использовать grep (), используя ссылки на массивы, но я не думаю, что достаточно хорошо понимаю массивы, хэши и ссылки, чтобы сделать это правильно.Мои неудачные попытки grep () приведены ниже.Я был бы признателен за любые идеи о том, как сделать это правильно.

Я считаю, что проблема в том, что я не знаю, как ссылаться на поле id во втором массиве хэшей.Большинство примеров использования grep (), которые я видел, это просто просмотр всего массива, как вы это делаете с обычным grep (1).Мне нужно перебрать один массив, проверяя каждое из значений из поля id с полем id из другого массива.

  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;
  }

Я также пробовал варианты в grep () выше:

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

Ответы [ 3 ]

3 голосов
/ 09 марта 2019

Существует несколько библиотек, которые сравнивают массивы. Тем не менее, ваше сравнение включает в себя сложные структуры данных (массивы имеют хэш-ссылки в качестве элементов), и это по крайней мере усложняет использование всех известных мне модулей.

Итак, вот способ сделать это вручную. Я использую показанный массив и его копию с одним измененным значением.

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;

Это правильно идентифицирует элемент в @a2, который не существует в @a1xxx в отметке времени).

Примечания

  • Это находит, что элементы одного массива не находятся в другом, а не полная разница между массивами. Это то, о чем конкретно спрашивает вопрос.

  • Сравнение основывается на деталях вашей структуры данных (hashref); от этого не уйти, если только вы не хотите использовать более полные библиотеки (например, Test::More).

  • Используется сравнение строк, ne, даже для чисел и временных меток. Посмотрите, имеет ли смысл в ваших реальных данных использовать более подходящие сравнения для конкретных элементов.

  • Поиск по всему списку для каждого элемента списка - это алгоритм O (N * M) . Решения такой (квадратичной) сложности пригодны для использования, пока данные не слишком велики; однако, как только данные становятся достаточно большими, чтобы увеличение размера имело явные эффекты, они быстро ломаются (замедляются до такой степени, что становятся бесполезными). Время, чтобы почувствовать это в вашем случае.

    Здесь существует O (N + M) подход, использующий хэши, показанные в ответе икегами. Это намного лучше алгоритмически, если данные достаточно велики, чтобы их можно было показать. Однако, поскольку ваш массив содержит сложную структуру данных (hashrefs), нужно немного поработать, чтобы создать работающую программу, особенно если мы не знаем данных. Но если ваши данные значительны, вы наверняка захотите реализовать это.


Некоторые комментарии по фильтрации.

Вопрос правильно отмечает, что для каждого элемента массива, поскольку он обрабатывается в grep, весь другой массив должен быть проверен.

Это делается в теле grep с использованием none из List :: Util . Он возвращает true, если код в его блоке оценивает false для всех элементов списка; таким образом, если «ни один» из элементов не удовлетворяет этому коду. Это основа требования: элемент не должен быть найден в другом массиве.

Требуется осторожность со стандартной $_ переменной , так как она используется как grep и none.

В блоке grep $_ используется псевдоним текущего обрабатываемого элемента списка, так как grep проходит через них один за другим; мы сохраняем его в именованную переменную ($e2). Затем приходит none и в своем блоке «вступает во владение» $_, присваивая ему элементы @a1 по мере их обработки. Текущий элемент @a2 также доступен, так как мы скопировали его в $e2.

Тест, выполненный в none, помещается в подпрограмму, которую я называю hr_eq, чтобы подчеркнуть, что она специально предназначена для сравнения на равенство (элементов в) хэш-ссылок.

Именно в этом сабе детали могут быть изменены. Во-первых, вместо того, чтобы прямо использовать ne для значений для каждого ключа, вы можете добавить пользовательские сравнения для определенных ключей (числа должны использовать == и т. Д.). Затем, если ваши структуры данных изменятся, это то место, где вы будете корректировать специфику.

2 голосов
/ 09 марта 2019

Вы можете использовать grep.

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;
}

Но это решение O (N * M), хотя существует решение O (N + M), которое было бы намного быстрее.

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}};
}

Если оба ваших подключения к базе данных находятся в одной и той же базе данных, это может быть сделано более эффективно в самой базе данных.

  1. Создать временную таблицу с тремя полями: id, old_count (DEFAULT 0) и new_count (DEFAULT 0).
  2. INSERT OR UPDATE из старой таблицы во временную таблицу, увеличивая old_count в процессе.
  3. INSERT OR UPDATE из новой таблицы во временную таблицу, увеличивая new_count в процессе.
  4. SELECT строки временной таблицы, имеющие 0 для old_count или 0 для new_count.
1 голос
/ 16 марта 2019
select id,title,created from mos_content
     LEFT JOIN xugc_content USING(id)
     WHERE xugc_content.id IS NULL;

Дает вам строки, которые находятся в mos_content, но не в xugc_content.

Это даже меньше, чем код Perl.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...