26855


python, ruby ではハッシュの要素でのスライスが1行でできますが、perl ではどうやるのでしょうか？  
「perl hash slice」 で検索しても、hash のキーでスライスする方法しか見つかりませんでした。

やりたいのは下記のようなことです。

ruby

[code]

    [6] pry(main)> hash = (0..3).map { |x| [x, x*2] }.to_h
    => {0=>0, 1=>2, 2=>4, 3=>6}
    [7] pry(main)> hash.select { |k, v| v > 2 }
    => {2=>4, 3=>6}
    
[/code]

python

[code]

    In [26]: hash = { x:x*2 for x in range(4) }
    
    In [27]: hash
    Out[27]: {0: 0, 1: 2, 2: 4, 3: 6}
    
    In [28]: { k:v for k, v in hash.items() if v > 2 }
    Out[28]: {2: 4, 3: 6}
    
[/code]

ruby も python も、(key, value) の組としてループできますが、  
perl だとリストなので、同じようには扱えなくて、悩んでいます。

自分で考えたのは、[key, value]の配列のリストに変換してから、ループする方法ですが、  
標準で用意されていないとも思えないので、質問させていただきました。


CC BY-SA 3.0
2016-06-19T00:27:27.157
26844
2016-06-19T08:01:41.083
7471
question
perl
Perl のハッシュで、key ではなく value でのスライシングを1行で書けますか？


以下は map を使う方法です。

[code]

    use Data::Dumper;
    
    %hash = map { $_ => $_ * 2 } 0..3;
    map { $hash2{$_} = $hash{$_} if $hash{$_} > 2 } keys %hash;
    
    print Dumper(\%hash);
    print Dumper(\%hash2);
    
[/code]

**実行結果**

[code]

    $VAR1 = {
              '3' => 6,
              '0' => 0,
              '2' => 4,
              '1' => 2
            };
    $VAR1 = {
              '2' => 4,
              '3' => 6
            };
    
[/code]


CC BY-SA 3.0
2016-06-19T04:36:08.337
26847
2016-06-19T04:36:08.337
26844
answer


Perlのmapでは、ブロックが空リストを返すことでその要素を除いた結果になります（`perldoc -f map`参照）。

[code]

    my %hash = map { $_, $_ * 2 } 0..3;
    my %hash2 = map { $hash{$_} > 2 ? ($_, $hash{$_}) : () } keys %hash;
    
[/code]


CC BY-SA 3.0
2016-06-19T05:27:09.457
26853
2016-06-19T05:27:09.457
4010
26844
answer


grep でフィルタして map で生成 というのが個人的にはわかりやすいかなぁと思います

[code]

    my %hash = map{ $_ => $_*2 } 0..3;
    my %hash2 = map { $_ => $hash{$_} } grep { $hash{$_} > 2 } keys %hash;
    
[/code]


CC BY-SA 3.0
2016-06-19T06:31:59.703
26855
2016-06-19T06:31:59.703
5047
26844
answer


ハッシュをループするのにv5.12以降であればeachが使えますが、  
わかりずらい副作用があるので基本はmztnsさんのようにkeyを軸にします。

どうしてもハッシュのkeyとvalueをループ変数に取りたい場合は、spliceが使えます。

[code]

    my %hash = map { $_ => $_ * 2 } 0 .. 3;
    
    #
    # ハッシュはリストコンテキストで呼ばれると
    # 偶数インデックスがkey、奇数インデックスがvalueのリストを得られる。
    # perlのv5.18以降はハッシュの順序がランダムになる点に注意。
    #
    my @hash_as_array = %hash;
    
    while (my ($key, $value) = splice @hash_as_array, 0, 2) {
        next unless $value > 2;
        print "$key => $value\n";
    }
    
[/code]

ただし、spliceは破壊的なので、上の`@hash_as_array`は一時的な変数としてしか扱えません。

スコープを気にする場合は`List::MoreUtils`の`natatime`を使ってもいいです。

[code]

    use List::MoreUtils qw(natatime);
    
    my %hash = map { $_ => $_ * 2 } 0 .. 3;
    
    my $iter = natatime(2, %hash);
    while (my ($key, $value) = $iter->()) {
        next unless $value > 2;
        print "$key => $value\n";
    }
    
[/code]

短いコードなので、List::MoreUtilsが使えない場合でも直接定義してしまうのもありです。

[code]

    sub natatime ($@) {
        my ($n, @list) = @_;
        return sub { splice @list, 0, $n };
    }
    
    my %hash = map { $_ => $_ * 2 } 0 .. 3;
    
    my $iter = natatime(2, %hash);
    while (my ($key, $value) = $iter->()) {
        next unless $value > 2;
        print "$key => $value\n";
    }
    
[/code]


CC BY-SA 3.0
2016-06-19T07:56:14.663
26857
2016-06-19T08:01:41.083
2016-06-19T08:01:41.083
62
62
26844
answer