열두번째 날: 두 해시 합치기

저자

@keedi - Seoul.pm 리더, Perl덕후, 거침없이 배우는 펄의 공동 역자, keedi.k at gmail.com

시작하며

펄(Perl)에서 해시(hash)는 무척 유용한 자료구조입니다. 스크립트 언어의 가장 큰 특징이자 장점 중 하나는 배열이나 해시와 같은 복잡한 자료구조를 추가적인 라이브러리 적재없이 언어 내부에 구현해서 간단히 사용할 수 있다는 점입니다. 이러다보니 자연스럽게 계층적 자료인 경우 해시를 사용해서 저장하게 되는데 사용하다 보면 두 해시의 자료를 합쳐야 할 경우가 종종 있습니다. 단순한 해시라면 고민없이 해결하겠지만, 깊은 자료구조를 가진 해시라면 한참을 고민하거나 결국 합치는 루틴을 만들어야 하곤 합니다. 이런 류의 일은 꽤나 빈번하며, 역시 우리의 펄 선배들은 비슷한 생각을 하고 이미 바퀴를 발명해놓았지요.

준비물

필요한 모듈은 다음과 같습니다.

직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.

$ sudo cpan Hash::Merge

사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.

$ cpan Hash::Merge

사용법

Hash::Merge 모듈은 merge() 함수를 지원합니다. 우선 모듈 적재시 merge() 함수를 명시해서 이름공간(namespace) 지정없이 사용할 수 있도록 합니다. 물론 명시하지 않아도 Hash::Merge::merge()처럼 이름공간을 명시해서 사용해도 됩니다.

use Hash::Merge qw( merge );

이제 유사하지만 조금은 다른 두 해시인 %conf1%conf2를 만듭니다.

my %conf1 = (
    time_zone => 'Asia/Seoul',
    category  => {
        jacket => { str => '상의',     price => 10000 },
        pants  => { str => '바지',     price => 10000 },
        shirt  => { str => '와이셔츠', price => 5000  },
        shoes  => { str => '구두',     price => 5000  },
        tie    => { str => '넥타이',   price => 2000  },
    },
);

my %conf2 = (
    theme    => 'ace',
    category => {
        jacket => { str => '재킷',   price => 10000 },
        shirt  => { str => '셔츠',   price => 5000  },
        shoes  => { str => '신발',   price => 5000  },
        skirt  => { str => '치마',   price => 10000 },
        tie    => { str => '넥타이', price => 2000  },
    },
);

합치는 법은 정말 간단합니다. %conf1 해시와 %conf2 해시를 합쳐보죠.

my %merged_conf = %{ merge( \%conf1, \%conf2 ) };

합친 결과 해시를 풀어본 결과를 코드로 표현하면 다음과 같습니다.

my %merged_conf1 = (
    time_zone => 'Asia/Seoul',
    theme     => 'ace',
    category  => {
        jacket => { str => '상의',     price => 10000 },
        pants  => { str => '바지',     price => 10000 },
        shirt  => { str => '와이셔츠', price => 5000  },
        shoes  => { str => '구두',     price => 5000  },
        skirt  => { str => '치마',     price => 10000 },
        tie    => { str => '넥타이',   price => 2000  },
    },
);

잘 살펴보면 %conf2 해시에 있던 theme 키와 category.skirt 키에 해당하는 값이 추가됨을 확인할 수 있습니다. 즉 %conf1 해시의 값을 기준으로 %conf2의 값이 추가되되, 동일한 키의 값이 있을 경우 %conf1의 값을 유지한 것이죠. 호출할 때 %conf1, %conf2의 순서로 매개변수를 사용했기 때문에 Hash::Merge는 기본적으로는 왼쪽 해시 값을 우선함을 알 수 있습니다. 오른쪽 해시 값을 우선하려면 set_behavior() 함수를 이용합니다.

Hash::Merge::set_behavior('RIGHT_PRECEDENT');
my %merged_conf = %{ merge( \%conf1, \%conf2 ) };

'RIGHT_PRECEDENT'를 사용하면 오른쪽 해시를 우선하며, 아무것도 지정하지 않을 경우 기본값으로 'LEFT_PRECEDENT' 값을 사용합니다. 문서에는 set_behavior() 함수 대신 set_set_behavior() 함수로 표시하기도 했는데 이는 오타이니 조심하세요. set_behavior() 함수가 지원하는 인자 종류는 다음과 같습니다.

좌, 우는 이미 확인했고 나머지 두 개의 경우 'STORAGE_PRECEDENT'는 간단히 저장된 큰 값을 우선한다는 의미입니다. 즉, 스칼라보다는 배열을, 배열보다는 해시를 우선한다는 것이죠. 더불어 'RETAINMENT_PRECEDENT'는 두 해시 값을 합칠때 기존 한 값을 버리지 않고 배열로 저장한다는 의미입니다.

조금 복잡해보이지만 꽤나 상식적으로 동작하기 때문에 몇번만 사용해보면 금방 이해할 수 있습니다. 기본 동작 자체가 합치는, 즉 머지(merge)인 관계로 동일한 키에 대해 빈 해시나 빈 배열을 정의해서 해당 키 값을 초기화할 수 없다는 점만 주의하세요.

my %old = (
    theme    => 'ace',
    category => {
        jacket    => { str => '상의', price => 10000 },
        pants     => { str => '바지', price => 10000 },
    },
);

my %new = (
    time_zone => 'Asia/Seoul',
    category  => {},
);

Hash::Merge::set_behavior('RIGHT_PRECEDENT');
my %result = %{ merge( \%old, \%new ) };

앞의 코드의 결과로 여러분은 category 키의 값이 빈 해시가 되길 바란다면 이것은 오해하고 있는 것입니다. 결과는 다음과 같습니다.

my %result = (
    theme     => 'ace',
    time_zone => 'Asia/Seoul',
    category  => {
        jacket => { str => '상의', price => 10000 },
        pants  => { str => '바지', price => 10000 },
    },
);

category 값을 비어있게 만들고 싶다면 명시적으로 undef를 사용해야 합니다.

my %old = (
    theme    => 'ace',
    category => {
        jacket => { str => '상의', price => 10000 },
        pants  => { str => '바지', price => 10000 },
    },
);

my %new = (
    time_zone => 'Asia/Seoul',
    category  => undef,
);

Hash::Merge::set_behavior('RIGHT_PRECEDENT');
my %result = %{ merge( \%old, \%new ) };

정리하며

펄(Perl) 프로그래밍 시 해시는 프로그래밍을 직관적이고 편하게 만들어주는 유용한 내장 도구입니다. 해시를 즐겨 사용하다보면 꼭 맞닥뜨리게 되는 두 해시의 내용 더하기는 비교적 간단히 보이지만, 합치는 루틴을 대충 작성할 경우, 해시의 복잡도가 커질수록 예상치 못한 버그를 맞닥뜨릴 것입니다. 두 해시를 합쳐야 할 일이 생긴다면 CPAN의 Hash::Merge 모듈을 잊지마세요.

Enjoy Your Perl! ;-)

EOT

blog comments powered by Disqus