首页 > 解决方案 > YAML 与自定义方法合并

问题描述

我正在尝试合并两个 yml:-

use Hash::Merge qw( merge );
use YAML;
$file1 = a.yml
$file2 = b.yml
my $load1=&YAML::LoadFile($file1);
my $load2=&YAML::LoadFile($file2);
my $merge_data = merge($load1, $load2);
my $out_yml = Dump $final_soc_cfg_request;

我在自定义合并机制中遗漏了什么吗?

标签: perldata-structures

解决方案


对于问题中显示的数据,这是使用Hash::Merge::add_behavior_spec的一种方法。

use warnings;
use strict;
use feature 'say';    
use Data::Dump qw(dd pp);
    
use Storable qw(dclone);
use YAML qw(LoadFile);
use Hash::Merge qw(merge);

die "Usage: $0 file1 file2\n" if @ARGV != 2;
my ($fname1, $fname2) = @ARGV;

my $yml1 = LoadFile($fname1);
my $yml2 = LoadFile($fname2);

# Naive merge doesn't do all that's wanted
#my $r = merge($yml1, $yml2); dd $r;

Hash::Merge::add_behavior_spec( {
    'SCALAR' => {
        'SCALAR' => sub { $_[0] },
        'ARRAY'  => sub { $_[0] },
        'HASH'   => sub { $_[0] },
    },
    'ARRAY' => {
        'SCALAR' => sub { [ @{ $_[0] }, $_[1] ] },
        'ARRAY'  => sub {
            # For each pair of hashrefs check whether three (of four) keys
            # have the same values; if so, merge them. If not then add them.
            my @res;
            # Scan: Which elements in each array to merge, and which with which
            my (%m1, %m2, %merge);
            my @a1 = @{$_[0]};
            my @a2 = @{$_[1]};
            I1: foreach my $i1 (0..$#a1) {
                I2: foreach my $i2 (0..$#a2) {
                    next if exists $m1{$i1} or exists $m2{$i2};
                    if (ref $a1[$i1] eq 'HASH' and ref $a2[$i2] eq 'HASH') {
                        for (qw(name project source)) {
                            next I2 if $a1[$i1]->{$_} ne $a2[$i2]->{$_};
                        }
                    }
                    # Three key-pairs are same so fourth ones need be merged
                    $m1{$i1} = $m2{$i2} = 1;
                    $merge{$i1} = $i2;
                }
            }

            # Now assemble/merge components as marked above
            my (%added_1, %added_2);       # more bookkeeping needed :(
            A1: foreach my $i1 (0..$#a1) {
                A2: foreach my $i2 (0..$#a2) {
                    next A2 if (exists $m1{$i1} and exists $m2{$i2})
                            or exists $added_2{$i2};
                    push @res, dclone $a2[$i2];
                    $added_2{$i2} = 1;
                }
                next A1 if exists $m1{$i1} or exists $added_1{$i1};   
                push @res, dclone $a1[$i1];
                $added_1{$i1} = 1;
            }

            foreach my $i (keys %merge) {
                push @res, 
                    Hash::Merge::_merge_hashes($a1[$i], $a2[$merge{$i}]);
            }
            \@res;
        },
        'HASH'  => sub { [ @{ $_[0] }, values %{ $_[1] } ] },
    },
    'HASH' => {
        'SCALAR' => sub { $_[0] },
        'ARRAY'  => sub { $_[0] },
        'HASH'   => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },
    },
}, 'Merge Arrayrefs Recursively As Well',);


my $res = merge($yml1, $yml2);

dd $res;

每个要合并的数据结构都是一个包含 hashref 的 arrayref。当它们被模块合并时,hashrefs 只是集中到一个arrayref 中,而可以想象该arrayref 中的一些hashrefs 可以进一步合并。†</sup>

这在上面的ARRAY-to-ARRAY规则中得到了补救,其中检查了 hashrefs 对是否应该合并,如果合并则标记。合并的标准如下。

每个 hahref 都有三个带有字符串值的键,另一个带有 arrayref 值。如果这三个键值对相同​​,则 hashrefs 应该与三个键值对合二为一,而它们的第四个键的 arrayrefs 应该合并为一个,即该键的值。

然后在第二遍中,那些被标记为要合并的那些将接受_merge_hashes例程,而其他则被添加到结果数组中。

这打印

{
  工具指针 => [
    {
      名称 => "tool_gen.config",
      项目 => "TOT",
      来源=>“等”,
      工具 => [{ name => "vipcat" }, { name => "log" }],
    },
    {
      名称 => "tool_log.config",
      项目 => "TOT",
      来源=>“等”,
      工具 => [
                   {名称=>“xc”},
                   {名称=>“测试”},
                   {名称=>“vr”},
                   {名称=>“arbgen2”},
                 ],
    },
  ],
}

我使用Data::Dump来显示复杂的数据结构,因为它默认简单和简洁。(我的选择;当然还有其他的,核心是Data::Dumper,所以已经安装了。)

这只是一个演示,因为我已经简化了一些事情,还使用了固定的硬编码键名列表,以使其适用于显示的数据集。请填写您的真实数据的详细信息。

请注意,必须为每个单独的数据集重新编码,因此必须针对输入数据的任何变化进行审查,并可能对其进行调整或显着更改。

另请参阅这篇文章这篇文章以获取其他几个示例。


†</sup> 不怪模块。无法分析数组元素是否或如何合并它们,因为没有通用标准。因此,它们被简单地添加到结果数组中,对于任何更具体的需求,都有add_behavior_spec.


推荐阅读