简介
把两个任意深度的hash合并成一个。
使用方式如下:
use Hash::Merge qw( merge );
my %a = (
'foo' => 1,
'bar' => [ qw( a b e ) ],
'querty' => { 'bob' => 'alice' },
);
my %b = (
'foo' => 2,
'bar' => [ qw(c d) ],
'querty' => { 'ted' => 'margeret' },
);
my %c = %{ merge( \%a, \%b ) };
Hash::Merge::set_behavior( 'RIGHT_PRECEDENT' );
# This is the same as above
Hash::Merge::specify_behavior(
{
'SCALAR' => {
'SCALAR' => sub { $_[1] },
'ARRAY' => sub { [ $_[0], @{$_[1]} ] },
'HASH' => sub { $_[1] },
},
'ARRAY => {
'SCALAR' => sub { $_[1] },
'ARRAY' => sub { [ @{$_[0]}, @{$_[1]} ] },
'HASH' => sub { $_[1] },
},
'HASH' => {
'SCALAR' => sub { $_[1] },
'ARRAY' => sub { [ values %{$_[0]}, @{$_[1]} ] },
'HASH' => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },
},
},
'My Behavior',
);
# Also there is OO interface.
my $merge = Hash::Merge->new( 'LEFT_PRECEDENT' );
my %c = %{ $merge->merge( \%a, \%b ) };
# All behavioral changes (e.g. $merge->set_behavior(...)), called on an object remain specific to that object
# The legacy "Global Setting" behavior is respected only when new called as a non-OO function.
详情
在任何层级,都只从一个hash结构合并无冲突的键值对到另一个hash结构,如果遇到冲突的结构则会根据特定的配置进行处理。因为hash结构是可以深度嵌套的,所以任何层级的hash结构都会被使用同样的方法进行递归的合并。
注:自引用的hash,或是嵌套引用的hash都无法被正确的处理。
在hash结构中的值,会被当成hashref、arrayref或scalar来处理。默认情况下,在进行合并之前,会先使用Clone
模块对数据进行“克隆”得到一个副本,然后对数据的副本进行合并操作。当然了,如果有必要我们可以改变这种默认行为,使用原始数据进行合并操作。(参数set_clone_behavior
方法)
由于在很多时候,即使hash的键值产生了冲突用户也想要进行正解的合并;Hash::Merge
为用户提供了几常用的处理方式,并且也预留了供用户自己的扩展的接口。详情如下:
Left Precedence(左值优先)
这是默认行为。
在这种配置下,绑定在左值上的hash永远不会丢失。所有正常(没有冲突)的右值都会被合并到左值上。
my $merge = Hash::Merge->new();
my $merge = Hash::Merge->new('LEFT_PRECEDENT');
$merge->set_set_behavior('LEFT_PRECEDENT')
Hash::Merge::set_set_behavior('LEFT_PRECEDENT')
Right Precedence(右值优先)
和“左值优先”一样,只是这里是右值上的hash永远不会丢失,并且所有正常(没有冲突)的左值都会被合并到右值上。
my $merge = Hash::Merge->new('RIGHT_PRECEDENT');
$merge->set_set_behavior('RIGHT_PRECEDENT')
Hash::Merge::set_set_behavior('RIGHT_PRECEDENT')
Storage Precedence(存储优先)
如果冲突的键有两个不同类型的值,则更“大”的类型的会被保留下来。array比scalar更“大”,hash比其他其他类型都“大”。这时,较“小”的类型会尝试合并到较大的类型中,如果无法合并,较“小”类型的数据会被丢弃。
my $merge = Hash::Merge->new('STORAGE_PRECEDENT');
$merge->set_set_behavior('STORAGE_PRECEDENT')
Hash::Merge::set_set_behavior('STORAGE_PRECEDENT')
Retainment Precedence(Retainment 优先)
在这种行为下,不会有任何值丢失。scalar会被加入到数组中,scalar和array都可以填充到hash中。
my $merge = Hash::Merge->new('RETAINMENT_PRECEDENT');
$merge->set_set_behavior('RETAINMENT_PRECEDENT')
Hash::Merge::set_set_behavior('RETAINMENT_PRECEDENT')
方法说明
merge(<hashref>,<hashref)
使用特定的规则把两个hash合并成一个新的hash,并返回。
_hashify( <scalar>|<arrayref> ) -- INTERNAL FUNCTION
返回一个从 scalar 和 array 创建的hash;为每个scalar或array中的每一个元素创建一个键值对,键和值都是它本身。
_hashify(3) ## {3=>3}
_hashify([2,3]) ## {2=>2,3=>3}
_merge_hashes( <hashref>, <hashref> ) -- INTERNAL FUNCTION
实际上是针对每个 key 的 value 重复调用 merge
方法。
sub _merge_hashes {
my $self = &_get_obj; # '&' + no args modifies current @_
my ( $left, $right ) = ( shift, shift );
if ( ref $left ne 'HASH' || ref $right ne 'HASH' ) {
carp 'Arguments for _merge_hashes must be hash references';
return;
}
my %newhash;
foreach my $leftkey ( keys %$left ) {
if ( exists $right->{$leftkey} ) {
$newhash{$leftkey} = $self->merge( $left->{$leftkey}, $right->{$leftkey} );
}
else {
$newhash{$leftkey} = $self->{clone} ? $self->_my_clone( $left->{$leftkey} ) : $left->{$leftkey};
}
}
foreach my $rightkey ( keys %$right ) {
if ( !exists $left->{$rightkey} ) {
$newhash{$rightkey} = $self->{clone} ? $self->_my_clone( $right->{$rightkey} ) : $right->{$rightkey};
}
}
return \%newhash;
}
set_clone_behavior( <scalar> )
在合并之前,数据是否被复制。如果是 true ,则在合并前会复制原始数据得到副本,并对副本进行合并。如果是 false,则直接对原始数据进行合并。默认情况下是 true。
get_clone_behavior( )
返回当前的 复制 行为的配置。
set_behavior( <scalar> )
指定具体的 合并 行为。参数 scalar 必须是已经定义的值,如:LEFT_PRECEDENT, RIGHT_PRECEDENT,STORAGE_PRECEDENT,RETAINMENT_PRECEDENT
get_behavior( )
返回当前正在被Hash::Merge使用的 行为 配置。
specify_behavior( <hashref>, [<name>] )
为 Hash::Merge 指定一个自定义的合并行为。hashref 必须定义3个键(SCALAR,ARRAY,HASH)。其中每个键的值同样是一个包含三个键(SCALAR,ARRAY,HASH)的hash,不仅如此,并且内层hashref每个键的值都必须是一个 coderefs 。这些 coderefs 被调用时会传两个参数(左值和右值),它的功能就是合并这两个值,并返回一个scalar,arrayref或hashref。如果有必要,可以使用函数_hashify和_merge_hashes作为这些辅助函数。
例如,你想添加左值标量到右值的数组中,你可以有你的行为规范包括:
%spec = ( ...SCALAR => { ARRAY => sub { [ $_[0], @$_[1] ] }, ... } } );
内置行为
下面这些是每个内部行为在各种情况下如何工作的说明,其中$a是左值,$b是右值。
LEFT TYPE RIGHT TYPE LEFT_PRECEDENT RIGHT_PRECEDENT
SCALAR SCALAR $a $b
SCALAR ARRAY $a ( $a, @$b )
SCALAR HASH $a %$b
ARRAY SCALAR ( @$a, $b ) $b
ARRAY ARRAY ( @$a, @$b ) ( @$a, @$b )
ARRAY HASH ( @$a, values %$b ) %$b
HASH SCALAR %$a $b
HASH ARRAY %$a ( values %$a, @$b )
HASH HASH merge( %$a, %$b ) merge( %$a, %$b )
LEFT TYPE RIGHT TYPE STORAGE_PRECEDENT RETAINMENT_PRECEDENT
SCALAR SCALAR $a ( $a ,$b )
SCALAR ARRAY ( $a, @$b ) ( $a, @$b )
SCALAR HASH %$b merge( hashify( $a ), %$b )
ARRAY SCALAR ( @$a, $b ) ( @$a, $b )
ARRAY ARRAY ( @$a, @$b ) ( @$a, @$b )
ARRAY HASH %$b merge( hashify( @$a ), %$b )
HASH SCALAR %$a merge( %$a, hashify( $b ) )
HASH ARRAY %$a merge( %$a, hashify( @$b ) )
HASH HASH merge( %$a, %$b ) merge( %$a, %$b )
注:merger 表示调用了 _merge_hashes, hashify 表示调用了 _hashify。
示例
稍候更新,请关注。