Объединение двух файлов yml не обрабатывает дубликаты?

Я пытаюсь объединить 2 файла yml, используя модуль Hash::Merge perl. И пытаюсь сбросить его в файл yml, используя Dump из модуля YMAL.

use strict;
use warnings;
use Hash::Merge qw( merge );
Hash::Merge::set_behavior('RETAINMENT_PRECEDENT');
use File::Slurp qw(write_file);
use YAML;
my $yaml1 = $ARGV[0];
my $yaml2 = $ARGV[1];
my $yaml_output = $ARGV[2];
my $clkgrps = &YAML::LoadFile($yaml1);
my $clkgrps1 = &YAML::LoadFile($yaml2);
my $clockgroups = merge($clkgrps1, $clkgrps);
my $out_yaml = Dump $clockgroups;
write_file($yaml_output, { binmode => ':raw' }, $out_yaml);

После слияния файла yml я мог видеть повторяющиеся записи, т. е. следующий контент был одинаковым в двух файлах yml. При слиянии он рассматривает их как разные записи. Есть ли у нас неявный способ обработки дубликатов?


person RAMA    schedule 19.05.2021    source источник


Ответы (1)


Структуры данных, полученные из файлов YAML, обычно содержат ключи со значениями, являющимися ссылками на массивы с хэш-ссылками. В вашем тестовом случае это ссылка на массив для ключа test.

Тогда такой инструмент, как Hash::Merge, может добавлять только хеш-ссылки к массиву ссылок, принадлежащих тому же ключу; он не предназначен для сравнения элементов массива. Поэтому вам нужно сделать это самостоятельно, чтобы удалить дубликаты или применить к данным какие-либо определенные правила по вашему выбору.

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

В Perl существует несколько способов сериализации данных. Я бы рекомендовал JSON::XS как очень быстрый инструмент с выводом, который можно используется любым языком и инструментом. (Но, конечно, изучите другие, которые могут лучше соответствовать вашим конкретным потребностям.)

Простой полный пример с использованием ваших тестовых случаев

use strict;
use warnings;
use feature 'say';
use Data::Dump qw(dd pp);

use YAML;
use JSON::XS;
use Hash::Merge qw( merge );
#Hash::Merge::set_behavior('RETAINMENT_PRECEDENT');  # irrelevant here

die "Usage: $0 in-file1 in-file2 output-file-name\n" if @ARGV != 3;

my ($yaml1, $yaml2, $yaml_out) = @ARGV;

my $hr1 = YAML::LoadFile($yaml1);
my $hr2 = YAML::LoadFile($yaml2);
my $merged = merge($hr2, $hr1);
#say "merged: ", pp $merged;

for my $key (keys %$merged) {
    # The same keys get overwritten
    my %uniq = map { encode_json $_ => 1 } @{$merged->{$key}};
    
    # Overwrite the arrayref with the one without dupes
    $merged->{$key} = [ map { decode_json $_ } keys %uniq ];
}
dd $merged;

# Save the final structure...

Более сложные структуры данных требуют более разумного обхода; рассмотрите возможность использования инструмента для этого.

С файлами, как показано в вопросе, это печатает

{
  test => [
    { directory => "LIB_DIR", name => "ObsSel.ktc", project => "TOT" },
    { directory => "MODEL_DIR", name => "pipe.v", project => "TOT" },
    {
      directory => "PCIE_LIB_DIR",
      name => "pciechip.ktc",
      project => "PCIE_MODE",
    },
    { directory => "NAME_DIR", name => "fame.v", project => "SINGH" },
    { directory => "TREE_PROJECT", name => "Syn.yml", project => "TOT" },
  ],
}

(Я использую Data::Dump для отображения сложных данных из-за их простоты и компактного вывода по умолчанию. .)

Если есть проблемы с сериализацией и сравнением целых структур, рассмотрите возможность использования какого-либо дайджеста (контрольной суммы, хеширования).

Другим вариантом было бы сравнение структур данных в том виде, в котором они есть, для устранения дубликатов вручную. Для сравнения сложных структур данных мне нравится использовать Test::More, который очень хорошо работает для простых сравнений вне какого-либо тестирования. Но, конечно, есть и специальные инструменты, такие как Data::Compare.


Наконец, вместо ручной обработки результата наивного merge, как описано выше, можно закодировать желаемое поведение, используя Hash::Merge::add_behavior_spec и затем пусть все это сделает модуль. Конкретные примеры использования этой функции см., например, в этой публикации и этот пост.

Обратите внимание, что в этом случае вы по-прежнему пишете весь код для выполнения работы, как описано выше, но модуль снимает с вас часть механики.

person zdim    schedule 20.05.2021