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閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容