tags:

views:

446

answers:

8

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.

+6  A: 

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;
DVK
or to avoid map in void context: $array[$_] = $hash{$_} for keys %hash;
runrig
That'd have to be a `sort { $a <=> $b }`. Remember that `sort` defaults to string comparisons.
Zaid
+1  A: 
amphetamachine
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
@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].
amphetamachine
+1  A: 

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};
Rob Kennedy
A: 

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.

Eric Strom
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.
daotoad
+3  A: 

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};
FM
Or `my $n = $#{[keys %hash]}` if you have an aversion to constant-substraction...
Zaid
+5  A: 

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'
        ];
Ether
No `->` operator for the hashref?
Zaid
@Zaid: nope, that's not how you use slices in hashrefs.
Ether
Silly me, it's getting cast as an array, which is why the arrow operator isn't needed.
Zaid
A: 

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)...

Zaid
A: 
@a = @h{sort { $a <=> $b } keys %h};
codeholic