How to pass an array and hash to a subroutine

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • endure10
    New Member
    • Feb 2011
    • 5

    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.


    Code:
    %var = ();
    while ($ln1 = <FH>) {                                              # creates keys with first element of string (test name with no arguments)
      @ln1 = split /\s+/, $ln1;                                        
      my $ln1 = shift @ln1;
      $var{$ln1}++;                                                       # value of each key set to 1
    } 
    
    @files = (file1, file2, file3, file4);
    foreach (@files) {
      open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
      @line = <FILE>;
      close FILE;
      &compare(@line, %var);
    }
    
    sub compare (\@\%){
      my @line = shift @_;
      my %var = shift @_;
      foreach $ln (@line) {
        if (exists($var{$ln})) {
          $var{$ln}++;
        }   
      }
    }
  • NetDynamic
    New Member
    • Feb 2011
    • 27

    #2
    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

    Comment

    • endure10
      New Member
      • Feb 2011
      • 5

      #3
      Hi,
      Well the conditional statement in line 20:
      if (exists($var{$l n}))
      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.

      Comment

      • miller
        Recognized Expert Top Contributor
        • Oct 2006
        • 1086

        #4
        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.

        Code:
        use Data::Dumper;
        
        use strict;
        
        my %hash = map {$_ => 1} qw(e f g h i j k);
        
        compare(\%hash, qw(a d f));
        compare(\%hash, qw(e h i));
        compare(\%hash, qw(c d e));
        compare(\%hash, qw(g h i));
        
        print Dumper(\%hash);
        
        sub compare {
        	my $hash = shift;
        	my @array = @_;
        	
        	foreach (@array) {
        		if (exists $hash->{$_}) {
        			$hash->{$_}++;
        		}
        	}
        	
        	return;
        }
        
        1;
        
        __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

        Comment

        • endure10
          New Member
          • Feb 2011
          • 5

          #5
          Thanks Miller.

          I am trying to implement the sample code for referencing and dereferencing but I run into the error of
          Code:
          Use of uninitialized value in hash element
          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.

          Comment

          • miller
            Recognized Expert Top Contributor
            • Oct 2006
            • 1086

            #6
            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

            Comment

            • endure10
              New Member
              • Feb 2011
              • 5

              #7
              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

              Code:
                   if (exists $hash->{$_}) {
              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
              Code:
              Use of uninitialized value in concatenation (.) or string

              Comment

              • miller
                Recognized Expert Top Contributor
                • Oct 2006
                • 1086

                #8
                Where is all of your newly updated code?

                Comment

                • endure10
                  New Member
                  • Feb 2011
                  • 5

                  #9
                  Code:
                  my @files = ($file1, $file2, $file3, $file4);
                  foreach (@files) {
                    open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
                    my @line = <FILE>;
                    close FILE;
                    &compare(\%var,\@line);
                  }
                  
                  sub compare {
                    my $var = shift;
                    my @line = @_;
                    foreach my $ln (@line) {
                      if (exists $var->{$ln}) {
                        $var->{$ln}++;
                        print "Matching keys are: $ln \n";
                      }  
                    }
                    return;
                  }
                  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)

                  Comment

                  • miller
                    Recognized Expert Top Contributor
                    • Oct 2006
                    • 1086

                    #10
                    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:

                    Code:
                    my @files = ($file1, $file2, $file3, $file4);
                    foreach (@files) {
                    	open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
                    	my @line = <FILE>;
                    	close FILE;
                    	compare(\%var, @line);
                    }
                     
                    sub compare {
                    	my $hashref = shift;
                    	my @line = @_;
                    	foreach my $ln (@line) {
                    		if (exists $hashref->{$ln}) {
                    			$hashref->{$ln}++;
                    			print "Matching keys are: $ln \n";
                    		}  
                    	}
                    	return;
                    }
                    Or pass it by reference like so:

                    Code:
                    my @files = ($file1, $file2, $file3, $file4);
                    foreach (@files) {
                    	open FILE, $_ or die "------ Could not open $_ testlist. \n"; 
                    	my @line = <FILE>;
                    	close FILE;
                    	compare(\%var, \@line);
                    }
                     
                    sub compare {
                    	my $hashref = shift;
                    	my $arrayref = shift;
                    
                    	foreach my $ln (@$arrayref) {
                    		if (exists $hashref->{$ln}) {
                    			$hashref->{$ln}++;
                    			print "Matching keys are: $ln \n";
                    		}  
                    	}
                    	return;
                    }
                    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

                    Comment

                    • miller
                      Recognized Expert Top Contributor
                      • Oct 2006
                      • 1086

                      #11
                      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:

                      Code:
                      #! /usr/bin/perl -w
                      
                      use strict;
                      
                      my $fileA = shift @ARGV;
                      my $fileB = shift @ARGV;
                      
                      my $output = "output.txt";
                      
                      # creates keys with first element of string (test name with no arguments)
                      
                      our %var = ();
                      open my $fh, $fileA or die "$fileA: $!";
                      while (my $line = <$fh>) {
                      	if ($line =~ /(\S+)/) {
                      		$var{$1} = 0; # Initialize
                      	}
                      }
                      close $fh;
                      
                      my @files = ($fileB);
                      foreach (@files) {
                      	my @lines = ();
                      	open my $fh, $_ or die "$_: $!";
                      	while (<$fh>) {
                      		chomp;
                      		push @lines, $_;
                      	}
                      	close $fh;
                      	
                      	compare(\%var, \@lines);
                      }
                      
                      sub compare {
                      	my $hashref = shift;
                      	my $arrayref = shift;
                      	foreach my $ln (@$arrayref) {
                      		if (exists $hashref->{$ln}) {
                      			$hashref->{$ln}++;
                      			print "Matching keys are: $ln \n";
                      		}
                      	}
                      	return;
                      }
                      
                      #open my $oh, '>', $output or die "$output: $!";
                      while (my ($key, $val) = each %var) {
                      	if ($val == 1) {
                      		print "$key is equal\n";
                      	} else {
                      		print "$key, $val is different\n";
                      	}
                      }

                      Comment

                      Working...