469,353 Members | 2,066 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,353 developers. It's quick & easy.

How to pass an array and hash to a subroutine

Hi everyone,

I'm working on a script that takes in a certain file A and compares it against several other files and prints any lines that exist in A but not in the other files. I was able to code a script so that File A is compared against 4 other files.

However, to improve code readability and compare File A against a variable number of files, I am trying to make the actual comparison process a subroutine so that it can be called the necessary number of times.

So my code takes in the first file and reads in every line, and makes a hash with the line as the key and its value 1. It then takes in each of the other files one by one and reads in the line from the file. The line that's read is used in a conditional statement to see if that key exists, and if it does, increments the value of that key by 1. In order to print only the differences of file A, I only print the hash keys that have values of 1.

The problem with my code is that although the array and hash is passed to the subroutine "compare," when the check to see if the key exists, the statement is always false for some reason. I believe the problem has something to do with the passing of the arguments into the subroutine because although I can access the array and hash successfully in the subroutine, the conditional statement is never satisfied.

So, I'm not sure if it's a perl syntax error or programmer but any help on this would be greatly appreciated.


Expand|Select|Wrap|Line Numbers
  1. %var = ();
  2. while ($ln1 = <FH>) {                                              # creates keys with first element of string (test name with no arguments)
  3.   @ln1 = split /\s+/, $ln1;                                        
  4.   my $ln1 = shift @ln1;
  5.   $var{$ln1}++;                                                       # value of each key set to 1
  6.  
  7. @files = (file1, file2, file3, file4);
  8. foreach (@files) {
  9.   open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
  10.   @line = <FILE>;
  11.   close FILE;
  12.   &compare(@line, %var);
  13. }
  14.  
  15. sub compare (\@\%){
  16.   my @line = shift @_;
  17.   my %var = shift @_;
  18.   foreach $ln (@line) {
  19.     if (exists($var{$ln})) {
  20.       $var{$ln}++;
  21.     }   
  22.   }
  23. }
  24.  
  25.  
Feb 19 '11 #1

✓ answered by miller

In reply to your private message, it appears you weren't chomp the lines of the file you were attempting to compare to. This meant that all the lines ended with a return character, and all of the words you tried to match did not contain such.

The following includes some cleaning up I personally would make to your code, but it probably still needs work:

Expand|Select|Wrap|Line Numbers
  1. #! /usr/bin/perl -w
  2.  
  3. use strict;
  4.  
  5. my $fileA = shift @ARGV;
  6. my $fileB = shift @ARGV;
  7.  
  8. my $output = "output.txt";
  9.  
  10. # creates keys with first element of string (test name with no arguments)
  11.  
  12. our %var = ();
  13. open my $fh, $fileA or die "$fileA: $!";
  14. while (my $line = <$fh>) {
  15.     if ($line =~ /(\S+)/) {
  16.         $var{$1} = 0; # Initialize
  17.     }
  18. }
  19. close $fh;
  20.  
  21. my @files = ($fileB);
  22. foreach (@files) {
  23.     my @lines = ();
  24.     open my $fh, $_ or die "$_: $!";
  25.     while (<$fh>) {
  26.         chomp;
  27.         push @lines, $_;
  28.     }
  29.     close $fh;
  30.  
  31.     compare(\%var, \@lines);
  32. }
  33.  
  34. sub compare {
  35.     my $hashref = shift;
  36.     my $arrayref = shift;
  37.     foreach my $ln (@$arrayref) {
  38.         if (exists $hashref->{$ln}) {
  39.             $hashref->{$ln}++;
  40.             print "Matching keys are: $ln \n";
  41.         }
  42.     }
  43.     return;
  44. }
  45.  
  46. #open my $oh, '>', $output or die "$output: $!";
  47. while (my ($key, $val) = each %var) {
  48.     if ($val == 1) {
  49.         print "$key is equal\n";
  50.     } else {
  51.         print "$key, $val is different\n";
  52.     }
  53. }
  54.  

10 5293
I havn't worked with Perl in forever since switching to PHP but you appear not be returning any value at all.You need to return the value and evaluate it or return TRUE or FALSE in your subroutine
Feb 20 '11 #2
Hi,
Well the conditional statement in line 20:
if (exists($var{$ln}))
is never successful and so, the values of the hashes are never incremented. So I was wondering if the syntax in lines 13 and 16-18 for passing the array and hash is correct, which I believe has nothing to do with the subroutine returning a value.
Feb 20 '11 #3
miller
1,089 Expert 1GB
To pass a hash or an array to a subroutine you must pass it by reference. Alternatively, you can also add a prototype to your sub, but you will still be passing by reference ultimately.

Then you simply have to decide if you want to dereference your parameters, or if you want to edit the copy of the passed data structures.

My advice to you is to avoid prototypes entirely, and just pass your variables by reference explicitly. Take a look at the below perl code that does a similar thing to what you are on some fake data.

Expand|Select|Wrap|Line Numbers
  1. use Data::Dumper;
  2.  
  3. use strict;
  4.  
  5. my %hash = map {$_ => 1} qw(e f g h i j k);
  6.  
  7. compare(\%hash, qw(a d f));
  8. compare(\%hash, qw(e h i));
  9. compare(\%hash, qw(c d e));
  10. compare(\%hash, qw(g h i));
  11.  
  12. print Dumper(\%hash);
  13.  
  14. sub compare {
  15.     my $hash = shift;
  16.     my @array = @_;
  17.  
  18.     foreach (@array) {
  19.         if (exists $hash->{$_}) {
  20.             $hash->{$_}++;
  21.         }
  22.     }
  23.  
  24.     return;
  25. }
  26.  
  27. 1;
  28.  
  29. __END__
Also note the included use strict; statement at the beginning of my code. All perl coders should include this in the scripts, as it will save you so much time letting perl do the syntax error checking for you.

- Miller
Feb 20 '11 #4
Thanks Miller.

I am trying to implement the sample code for referencing and dereferencing but I run into the error of
Expand|Select|Wrap|Line Numbers
  1. Use of uninitialized value in hash element
  2.  
at line 5 of the code I posted. I initialize the hash in line 1 so I don't understand why my code would return this error.

Once again, thank you for the help.
Feb 22 '11 #5
miller
1,089 Expert 1GB
Did you get the sample code I provided you working? Yes?

Do you understand where your problem was before?

If you are now changing your code, where is the new version with the error, and exactly what line number is the error reported on?

Finally did you add "use strict;" to the beginning of your program and therefore fix all the variable declarations with "my"? If you haven't done this, then you haven't done your due diligence yet. This is the number 1 thing you can do to get perl to help you find basic syntax errors in your program. Do this before anything else.

- Miller
Feb 22 '11 #6
Sorry I was trying to delete that previous post because that warning is irrelevant to this problem but I didn't know how.

Anyway, I did implement your method of passing the hash and array and also your variable naming convention. I checked to see if I could modify the values of the hash in the subroutine and everything checks out. However, my problem in your code equivalent would be at line 19 and is still the same as before

Expand|Select|Wrap|Line Numbers
  1.      if (exists $hash->{$_}) { 
  2.  
This line is never true and so lines that file A and other files never get omitted from being printed to the output. If I manually select a key for the hash, the value is a 1 (which is correct) and if print $_, I can see that that line is from the file. But, if I try to print a value of the hash using $_ by printing $var->{$_}, I receive the error
Expand|Select|Wrap|Line Numbers
  1. Use of uninitialized value in concatenation (.) or string
  2.  
Feb 22 '11 #7
miller
1,089 Expert 1GB
Where is all of your newly updated code?
Feb 22 '11 #8
Expand|Select|Wrap|Line Numbers
  1. my @files = ($file1, $file2, $file3, $file4);
  2. foreach (@files) {
  3.   open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
  4.   my @line = <FILE>;
  5.   close FILE;
  6.   &compare(\%var,\@line);
  7. }
  8.  
  9. sub compare {
  10.   my $var = shift;
  11.   my @line = @_;
  12.   foreach my $ln (@line) {
  13.     if (exists $var->{$ln}) {
  14.       $var->{$ln}++;
  15.       print "Matching keys are: $ln \n";
  16.     }  
  17.   }
  18.   return;
  19. }
  20.  
Sorry, I realize that the pass by reference for the array isn't working. I don't understand how the 2nd line of sub compare would pass the value of the array. (using your sample code as a reference)
Feb 22 '11 #9
miller
1,089 Expert 1GB
Easily fixed. You just need to study references and their associated syntax some more.

Either pass the array as a list of parameters like so:

Expand|Select|Wrap|Line Numbers
  1. my @files = ($file1, $file2, $file3, $file4);
  2. foreach (@files) {
  3.     open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
  4.     my @line = <FILE>;
  5.     close FILE;
  6.     compare(\%var, @line);
  7. }
  8.  
  9. sub compare {
  10.     my $hashref = shift;
  11.     my @line = @_;
  12.     foreach my $ln (@line) {
  13.         if (exists $hashref->{$ln}) {
  14.             $hashref->{$ln}++;
  15.             print "Matching keys are: $ln \n";
  16.         }  
  17.     }
  18.     return;
  19. }
Or pass it by reference like so:

Expand|Select|Wrap|Line Numbers
  1. my @files = ($file1, $file2, $file3, $file4);
  2. foreach (@files) {
  3.     open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
  4.     my @line = <FILE>;
  5.     close FILE;
  6.     compare(\%var, \@line);
  7. }
  8.  
  9. sub compare {
  10.     my $hashref = shift;
  11.     my $arrayref = shift;
  12.  
  13.     foreach my $ln (@$arrayref) {
  14.         if (exists $hashref->{$ln}) {
  15.             $hashref->{$ln}++;
  16.             print "Matching keys are: $ln \n";
  17.         }  
  18.     }
  19.     return;
  20. }
Either method works. Just note that passing a reference sometimes implies that you want to do operations that modify that very data structure. However, it also can be done for simple efficiency, as this way you're not duplicating the data structure inside the subroutine.

- Miller
Feb 22 '11 #10
miller
1,089 Expert 1GB
In reply to your private message, it appears you weren't chomp the lines of the file you were attempting to compare to. This meant that all the lines ended with a return character, and all of the words you tried to match did not contain such.

The following includes some cleaning up I personally would make to your code, but it probably still needs work:

Expand|Select|Wrap|Line Numbers
  1. #! /usr/bin/perl -w
  2.  
  3. use strict;
  4.  
  5. my $fileA = shift @ARGV;
  6. my $fileB = shift @ARGV;
  7.  
  8. my $output = "output.txt";
  9.  
  10. # creates keys with first element of string (test name with no arguments)
  11.  
  12. our %var = ();
  13. open my $fh, $fileA or die "$fileA: $!";
  14. while (my $line = <$fh>) {
  15.     if ($line =~ /(\S+)/) {
  16.         $var{$1} = 0; # Initialize
  17.     }
  18. }
  19. close $fh;
  20.  
  21. my @files = ($fileB);
  22. foreach (@files) {
  23.     my @lines = ();
  24.     open my $fh, $_ or die "$_: $!";
  25.     while (<$fh>) {
  26.         chomp;
  27.         push @lines, $_;
  28.     }
  29.     close $fh;
  30.  
  31.     compare(\%var, \@lines);
  32. }
  33.  
  34. sub compare {
  35.     my $hashref = shift;
  36.     my $arrayref = shift;
  37.     foreach my $ln (@$arrayref) {
  38.         if (exists $hashref->{$ln}) {
  39.             $hashref->{$ln}++;
  40.             print "Matching keys are: $ln \n";
  41.         }
  42.     }
  43.     return;
  44. }
  45.  
  46. #open my $oh, '>', $output or die "$output: $!";
  47. while (my ($key, $val) = each %var) {
  48.     if ($val == 1) {
  49.         print "$key is equal\n";
  50.     } else {
  51.         print "$key, $val is different\n";
  52.     }
  53. }
  54.  
Feb 23 '11 #11

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

2 posts views Thread by Anders Eriksson | last post: by
41 posts views Thread by Berk Birand | last post: by
5 posts views Thread by wilson | last post: by
6 posts views Thread by Wijaya Edward | last post: by
3 posts views Thread by keithl | last post: by
1 post views Thread by CARIGAR | last post: by
reply views Thread by zhoujie | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.