Perl 模块 Hash::Merge

简介

把两个任意深度的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。

示例

稍候更新,请关注。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容