Timmy
Timmy

Reputation: 12858

How can I get a only part of a hash in Perl?

Is there a way to get a sub-hash? Do I need to use a hash slice?

For example:

%hash = ( a => 1, b => 2, c => 3 );

I want only

%hash = ( a => 1, b => 2 );

Upvotes: 26

Views: 14284

Answers (7)

Chas. Owens
Chas. Owens

Reputation: 64939

Hash slices return the values associated with a list of keys. To get a hash slice you change the sigil to @ and provide a list of keys (in this case "a" and "b"):

my @items = @hash{"a", "b"};

Often you can use a quote word operator to produce the list:

my @items = @hash{qw/a b/};

You can also assign to a hash slice, so if you want a new hash that contains a subset of another hash you can say

my %new_hash;
@new_hash{qw/a b/} = @hash{qw/a b/};

Many people will use a map instead of hash slices:

my %new_hash = map { $_ => $hash{$_} } qw/a b/;

Starting with Perl 5.20.0, you can get the keys and the values in one step if you use the % sigil instead of the @ sigil:

use v5.20;
my %new_hash = %hash{qw/a b/};

Upvotes: 63

ephemient
ephemient

Reputation: 205014

Too much functional programming leads me to think of zip first.

With List::MoreUtils installed,

use List::MoreUtils qw(zip);

%hash = qw(a 1 b 2 c 3);
@keys = qw(a b);
@values = @hash{@keys};
%hash = zip @keys, @values;

Unfortunately, the prototype of List::MoreUtils's zip inhibits

zip @keys, @hash{@keys};

If you really want to avoid the intermediate variable, you could

zip @keys, @{[@hash{@keys}]};

Or just write your own zip without the problematic prototype. (This doesn't need List::MoreUtils at all.)

sub zip {
    my $max = -1;
    $max < $#$_ and $max = $#$_ for @_;
    map { my $ix = $_; map $_->[$ix], @_; } 0..$max;
}

%hash = zip \@keys, [@hash{@keys}];

If you're going to be mutating in-place,

%hash = qw(a 1 b 2 c 3);
%keep = map +($_ => 1), qw(a b);
$keep{$a} or delete $hash{$a} while ($a, $b) = each %hash;

avoids the extra copying that the map and zip solutions incur. (Yes, mutating the hash while you're iterating over it is safe... as long as the mutation is only deleting the most recently iterated pair.)

Upvotes: 3

Kjetil S.
Kjetil S.

Reputation: 3785

New in perl 5.20 is hash slices returning keys as well as values by using % like on the last line here:

my %population = ('Norway',5000000,'Sweden',9600000,'Denmark',5500000);
my @slice_values = @population{'Norway','Sweden'}; # all perls can do this
my %slice_hash   = %population{'Norway','Sweden'}; # perl >= 5.20 can do this!

Upvotes: 4

Hynek -Pichi- Vychodil
Hynek -Pichi- Vychodil

Reputation: 26141

Yet another way:

my @keys = qw(a b);
my %hash = (a => 1, b => 2, c => 3);
my %hash_copy;
@hash_copy{@keys} = @hash{@keys};

Upvotes: 6

jrockway
jrockway

Reputation: 42704

FWIW, I use Moose::Autobox here:

my $hash = { a => 1, b => 2, c => 3, d => 4 };
$hash->hslice([qw/a b/]) # { a => 1, b => 2 };

In real life, I use this to extract "username" and "password" from a form submission, and pass that to Catalyst's $c->authenticate (which expects, in my case, a hashref containing the username and password, but nothing else).

Upvotes: 3

Chris Lutz
Chris Lutz

Reputation: 75469

You'd probably want to assemble a list of keys you want:

my @keys = qw(a b);

And then use a loop to make the hash:

my %hash_slice;
for(@keys) {
  $hash_slice{$_} = %hash{$_};
}

Or:

my %hash_slice = map { $_ => $hash{$_} } @keys;

(My preference is the second one, but whichever one you like is best.)

Upvotes: 9

B Johnson
B Johnson

Reputation: 2566

A hash is an unordered container, but the term slice only really makes sense in terms of an ordered container. Maybe look into using an array. Otherwise, you may just have to remove all of the elements that you don't want to produce your 'sub-hash'.

Upvotes: -4

Related Questions