2007年10月24日 20:00 [Edit]

perl - 配列をハッシュキーとして扱う

camel

コメント欄にも「教科書解」がなかったので。

Perl勉強中 - YoshioriのBlog
#!/usr/bin/perl
use strict;
use warnings;

my @array = qw/1 2 3/;
my @array2 = qw/1 2 3/;

my %hash;
$hash{@array} = 'foo';  # ちょっと変更

print $hash{ @array2 }; # foo
みたいなこと出来ないのかな?

教科書解は、こちら。

$hash{ join($;, @array) } = 'foo';

これは、

$hash{1,2,3}     = 'foo';
$hash{qw/1 2 3/} = 'foo';

という、配列リテラルをキーにした場合と等価ですが、

$hash{@array} = 'foo';

とは等価ではありません

この$;というのは、こういう役割を持っています。

perldoc perlvar
$;  The subscript separator for multidimensional array emulation.
    If you refer to a hash element as
    
       $foo{$a,$b,$c}
    
    it really means
    
       $foo{join($;, $a, $b, $c)}
    
    But don’t put
    
       @foo{$a,$b,$c}      # a slice−−note the @
    
    which means
    
       ($foo{$a},$foo{$b},$foo{$c})
    
    Default is "\034", the same as SUBSEP in awk.  If your keys
    contain binary data there might not be any safe value for $;.
    (Mnemonic: comma (the syntactic subscript separator) is a semi‐
    semicolon.  Yeah, I know, it’s pretty lame, but $, is already
    taken for something more important.)
    
    Consider using "real" multidimensional arrays as described in
    perllol.

個人的には、あまり配列をキーにしたhashの必要性は感じないのですが、必要ならこういうやり方もあるということで。

「多次元ハッシュ」が必要なら、素直に

$hash{1}{2}{3} = 'foo'; # $hash{1}->{2}->{3} = 'foo' と等価

と書けばOKですし。

別解として、そういう機能をもったクラスを作って使うというのもあります。

package Hashlike::ArrayKey;

sub new {
    my $class = shift;
    my $self = bless {}, $class;
    $self->set(@_) if @_;
    return $self;
}

sub get {
    my $self = shift;
    return $self->{ join( $;, @_ ) };
}

sub set {
    my $self  = shift;
    my $value = pop;
    $self->{ join( $;, @_ ) } = $value;
}

1;
use Hashlike::ArrayKey;
my @array  = qw/1 2 3/;
my @array2 = qw/1 2 3/;

my $h = Hashlike::ArrayKey->new;
$h->set( @array => 'foo' );
print $h->get(@array2);

TMTOWTDI!

Dan the Perl Monger

追記:

もう一つおまけ。closureを使った方法。

sub make_akhash {
    return sub : lvalue {
        $val{join($;,@_)}
    }
}

my @array  = qw/1 2 3/;
my @array2 = qw/1 2 3/;

my $h = make_akhash();
$h->(@array) = 'foo';
print $h->(@array2);

この記事へのトラックバックURL

この記事へのコメント
今更ですが動きません(^^;
$;が値を持っていないんです。
% perl -e 'print "sep=|", $;,"|?n";'
sep=||
% perl -v
This is perl, v5.8.6 built for darwin-thread-multi-2level
...
なぜだぁ…
Posted by すがちゃん at 2007年11月07日 21:47