25

If I have a hash in Perl that contains complete and sequential integer mappings (ie, all keys from from 0 to n are mapped to something, no keys outside of this), is there a means of converting this to an Array?

I know I could iterate over the key/value pairs and place them into a new array, but something tells me there should be a built-in means of doing this.

2
  • 7
    RANT WARNING! Why is everyone wanting to sort the keys? There is no need to and it makes the algorithm so much slower! Sorting is slow! runrig's answer is the best one here. It will work if the hash is sparse. Arrays maintain order but they are random access structures. We aren't working with linked lists, people! Commented May 25, 2010 at 20:24
  • You're right: there is a way to do it built in. See Ether's answer. :) This question is another reason why there's a difference between list context and arrays. Coupled with slices, it lets you do conversions between lists and hashes without any special magic or iteration, and imho is one of the most powerful features of Perl. Commented May 25, 2010 at 20:32

11 Answers 11

24

You can extract all the values from a hash with the values function:

my @vals = values %hash;

If you want them in a particular order, then you can put the keys in the desired order and then take a hash slice from that:

my @sorted_vals = @hash{sort { $a <=> $b } keys %hash};
Sign up to request clarification or add additional context in comments.

Comments

23

If your original data source is a hash:

# first find the max key value, if you don't already know it:
use List::Util 'max';
my $maxkey = max keys %hash;

# get all the values, in order
my @array = @hash{0 .. $maxkey};

Or if your original data source is a hashref:

my $maxkey = max keys %$hashref;
my @array = @{$hashref}{0 .. $maxkey};

This is easy to test using this example:

my %hash;
@hash{0 .. 9} = ('a' .. 'j');

# insert code from above, and then print the result...
use Data::Dumper;
print Dumper(\%hash);
print Dumper(\@array);

$VAR1 = {
          '6' => 'g',
          '3' => 'd',
          '7' => 'h',
          '9' => 'j',
          '2' => 'c',
          '8' => 'i',
          '1' => 'b',
          '4' => 'e',
          '0' => 'a',
          '5' => 'f'
        };
$VAR1 = [
          'a',
          'b',
          'c',
          'd',
          'e',
          'f',
          'g',
          'h',
          'i',
          'j'
        ];

6 Comments

No -> operator for the hashref?
@Zaid: nope, that's not how you use slices in hashrefs.
Silly me, it's getting cast as an array, which is why the arrow operator isn't needed.
No casting or arrays are involved. The hash slice returns a list.
And this will work even if the hash keys aren't numeric?
|
15

OK, this is not very "built in" but works. It's also IMHO preferrable to any solution involving "sort" as it's faster.

map { $array[$_] = $hash{$_} } keys %hash; # Or use foreach instead of map

Otherwise, less efficient:

my @array = map { $hash{$_} } sort { $a<=>$b } keys %hash;

3 Comments

or to avoid map in void context: $array[$_] = $hash{$_} for keys %hash;
That'd have to be a sort { $a <=> $b }. Remember that sort defaults to string comparisons.
I'm not sure I see where I could avoid sort. That should be the case where I don't care about the order in the resulting array.
4

Perl does not provide a built-in to solve your problem.

If you know that the keys cover a particular range 0..N, you can leverage that fact:

my $n = keys(%hash) - 1;
my @keys_and_values = map { $_ => $hash{$_} } 0 .. $n;
my @just_values     = @hash{0 .. $n};

1 Comment

Or my $n = $#{[keys %hash]} if you have an aversion to constant-substraction...
2

This will leave keys not defined in %hashed_keys as undef:

# if we're being nitpicky about when and how much memory
# is allocated for the array (for run-time optimization):
my @keys_arr = (undef) x scalar %hashed_keys;

@keys_arr[(keys %hashed_keys)] =
    @hashed_keys{(keys %hashed_keys)};

And, if you're using references:

@{$keys_arr}[(keys %{$hashed_keys})] = 
    @{$hashed_keys}{(keys %{$hashed_keys})};

Or, more dangerously, as it assumes what you said is true (it may not always be true … Just sayin'!):

@keys_arr = @hashed_keys{(sort {$a <=> $b} keys %hashed_keys)};

But this is sort of beside the point. If they were integer-indexed to begin with, why are they in a hash now?

2 Comments

Good use of slices. Also good point about initial choice of data structure. Too bad you brought up sort, sort is slow (see my rant above) and error prone and is not desireable.
@daotoad - The first (and recommended) solution does not use sort. But, I completely agree about sort; it generates up to n-squared arbitrary function calls per invocation [hint: this is terrible].
0

As DVK said, there is no built in way, but this will do the trick:

my @array = map {$hash{$_}} sort {$a <=> $b} keys %hash;

or this:

my @array;

keys %hash;

while (my ($k, $v) = each %hash) {
    $array[$k] = $v
}

benchmark to see which is faster, my guess would be the second.

1 Comment

Well the first is going to be O(NlogN) on average, but up to O(N^2). The second method is simply O(N). No need for benchmarks.
0
@a = @h{sort { $a <=> $b } keys %h};

Comments

0

Combining FM's and Ether's answers allows one to avoid defining an otherwise unnecessary scalar:

my @array = @hash{ 0 .. $#{[ keys %hash ]} };

The neat thing is that unlike with the scalar approach, $# works above even in the unlikely event that the default index of the first element, $[, is non-zero.

Of course, that would mean writing something silly and obfuscated like so:

my @array = @hash{ $[ .. $#{[ keys %hash ]} };   # Not recommended

But then there is always the remote chance that someone needs it somewhere (wince)...

Comments

0
$Hash_value = 
{
'54' => 'abc',
'55' => 'def',
'56' => 'test',
};
while (my ($key,$value) = each %{$Hash_value})
{
 print "\n $key > $value";
}

Comments

0

We can write a while as below:

$j =0;
while(($a1,$b1)=each(%hash1)){
    $arr[$j][0] = $a1;
    ($arr[$j][1],$arr[$j][2],$arr[$j][3],$arr[$j][4],$arr[$j][5],$arr[$j][6]) = values($b1);
    $j++;
}

$a1 contains the key and $b1 contains the values In the above example i have Hash of array and the array contains 6 elements.

1 Comment

And what if the original hash contains 100 elements? Or if you don't know how many elements it contains?
-1

An easy way is to do @array = %hash

For example,

my %hash = (
    "0"  => "zero",
    "1" => "one",
    "2"  => "two",
    "3"  => "three",
    "4"  => "four",
    "5"  => "five",
    "6"  => "six",
    "7"  => "seven",
    "8"  => "eight",
    "9"  => "nine",
    "10"  => "ten",
);

my @array = %hash;

print "@array"; would produce the following output,

3 three 9 nine 5 five 8 eight 2 two 4 four 1 one 10 ten 7 seven 0 zero 6 six

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.