user110084
user110084

Reputation: 279

Perl: passing two hashes to sub

Below is a small sub to calculate distance between two points by passing two hashes each containing the x and y coordinates. I get a "syntax error near ]{" fatal error at the line calling the sub. Just started on Perl yesterday, not entirely sure what I am doing. How do I pass two hashes to a sub to return a value? Tried this without much success and not sure what I need to work on (hope it is ok to refer to an outside link).

%dot1 = ('x'=>5, 'y'=>6);
%dot2 = ('x'=>7, 'y'=>8);

sub dist {
    my (%hash1) = @_[0];
    my (%hash2) = @_[1];

    $dist = ((@_[0]{'x'}-@_[1]{'x'})**2 + (@_[0]{'y'}-@_[1]{'y'})**2)**0.5;
}


$D = dist(\%dot1,\%dot2);

Upvotes: 1

Views: 1810

Answers (3)

melpomene
melpomene

Reputation: 85767

First and foremost, you should start every file with

use strict;
use warnings;

This lets Perl catch the most obvious errors in code.


This part is mostly fine, but under use strict Perl will complain about %dot1 and %dot2 being undeclared (and without strict they will be implicitly global, which is usually not what you want):

%dot1 = ('x'=>5, 'y'=>6);
%dot2 = ('x'=>7, 'y'=>8);

Change it to

my %dot1 = ('x'=>5, 'y'=>6);
my %dot2 = ('x'=>7, 'y'=>8);

The call

$D = dist(\%dot1,\%dot2);

has the same problem: It should be

my $D = dist(\%dot1,\%dot2);

What it does is pass references to %dot1 and %dot2 to the sub dist.


    my (%hash1) = @_[0];

This line doesn't make much sense: @_[0] is a list slice, returning a list of elements of @_ corresponding to the indices 0. In other words, it's a one-element slice and better written as $_[0], accessing the single element directly.

But in either case it doesn't make sense to assign a single element to a hash. Perl will interpret it as a key and set the corresponding value to undef. Your call passed \%dot1 as the first argument, so $_[0] is a reference to a hash. By using it as a hash key, Perl will convert it to a string, yielding something like "HASH(0x0075ADD40)".

Your choices at this point are to either dereference the reference right there and make a copy:

    my %hash1 = %{ $_[0] };   # effectively performs %hash1 = %dot1

Or keep the reference and dereference it each time you need access to the hash:

    my $hashref1 = $_[0];     # $hashref1->{foo} accesses $dot1{foo} directly

    $dist = ((@_[0]{'x'}-@_[1]{'x'})**2 + (@_[0]{'y'}-@_[1]{'y'})**2)**0.5;

There are a few issues here. First, you don't need the (implicitly global) variable $dist. You just want to return a value from the sub, which can be done with return. Then, as explained above, @_[0] and @_[1] should be $_[0] and $_[1], respectively. Fixing that we get

    return (($_[0]{'x'} - $_[1]{'x'}) ** 2 + ($_[0]{'y'} - $_[1]{'y'}) ** 2) ** 0.5;

This does indeed work ($_[0]{'x'} is syntactic sugar for $_[0]->{'x'}, i.e. this expression dereferences the hash reference stored in $_[0] to reach the 'x' key of %dot1).

But we didn't use the variables we just created at all. Depending on which way you want to go, you should replace $_[0]{foo} by either $hash1{foo} or $hashref1->{foo} (and similar for $_[1] and %hash2/$hashref2).

Finally, instead of ** 0.5 we can just use sqrt.


Here's how I'd write it:

use strict;
use warnings;

sub dist {
    my ($p1, $p2) = @_;
    return sqrt(($p1->{x} - $p2->{x}) ** 2 + ($p1->{y} - $p2->{y}) ** 2);
}

my %dot1 = (x => 5, y => 6);
my %dot2 = (x => 7, y => 8);

my $D = dist(\%dot1, \%dot2);

print "Result: $D\n";

Upvotes: 5

Dave Cross
Dave Cross

Reputation: 69244

Many problems here, I'm afraid.

First you, need to add use strict and use warnings to your code. This will point out many errors. Mainly places where you use @array[index] but should have used $array[index]. You also don't declare any of your variables.

Fixing all of that gives me this code:

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

my %dot1 = ('x'=>5, 'y'=>6);
my %dot2 = ('x'=>7, 'y'=>8);

sub dist {
    my (%hash1) = $_[0];
    my (%hash2) = $_[1];

    my $dist = (($_[0]{'x'}-$_[1]{'x'})**2 + ($_[0]{'y'}-$_[1]{'y'})**2)**0.5;
}


my $D = dist(\%dot1,\%dot2);

say $D;

This still doesn't work. I get:

$ perl twohash
Reference found where even-sized list expected at twohash line 11.
Reference found where even-sized list expected at twohash line 12.
2.82842712474619

The errors are where you are assigning the two arguments you pass into dist().

my (%hash1) = $_[0];
my (%hash2) = $_[1];

You are passing references to hashes, not the hashes themselves (And you're right to do that), but this means that you get scalars, not hashes, in the subroutine. So those lines need to be:

my ($hash1) = $_[0];
my ($hash2) = $_[1];

Making those changes, the code now works and gives the result "2.82842712474619".

I'll just point out one further strangeness in your code - you assign the parameters to the function to two lexical variables ($hash1 and $hash2) but you then ignore those variables and instead go directly to @_ for this data. I expect you actually want:

my $dist = (($hash1->{'x'}-$hash2->{'x'})**2 + ($hash1->{'y'}-$hash2->{'y'})**2)**0.5;

Note, I've changed $_[0]{'x'} to $hash1->{'x'} as you have a reference to a hash.

All in all, this code is a bit of a mess and I suggest you go back to the very earliest chapters of whatever book you are learning Perl from.

Upvotes: 2

ssr1012
ssr1012

Reputation: 2589

Could you please try this:

use Data::Dumper;

my %dot1 = ('x'=>5, 'y'=>6);
my %dot2 = ('x'=>7, 'y'=>8);

sub dist {
    my %hash1 = @_[0];
    my %hash2 = @_[1];
   $dist = ((@_[0]->{'x'}-@_[1]->{'x'})**2 + (@_[0]->{'y'}-@_[1]->{'y'})**2)**0.5;
   }

$D = dist(\%dot1,\%dot2);

print $D;

Upvotes: -1

Related Questions