jonnyw83 has asked for the wisdom of the Perl Monks concerning the following question:

Hi PerlMonks,

Perl beginner here struggling with a small bit of code. I want to loop through the keys of a hash and check to see whether the key was present in a previous array. This works ok. Then based on whether the key is found add a value (again previously defined in a loop) to the array that the key points to within one of two different hashes. Both hashes have identical keys but should be used to store differing data based on the outcome of IF. The sub bit of code is:

foreach my $key ( keys (%hasha)) {if ( my @temp = grep ( /$key/, @prevarray)){push(@{$hasha{$key}}, +$value); next;} #closes if loop else { push (@{$hashb{$key}},$value); next;} #closes if loop } #close foreach loop

At the moment this appears to be adding every $value to each key within both arrays. So there is no filtering and I can not work out why. Any hints would be much appreciated

Replies are listed 'Best First'.
Re: Updating arrays within hashes
by moritz (Cardinal) on Feb 10, 2011 at 10:06 UTC
    For each iteration, only one of the if/else branches is executed.

    If the items shows up in both arrays, the two array references must be references to the same array. Compare:

    # two distinct array refs: $ perl -wE 'my $a = [1]; my $b = [1]; push @$a, 2; say for @$b' 1 # same array ref twice: $ perl -wE 'my $a = [1]; my $b = $a; push @$a, 2; say for @$b' 1 2

    In the second case, both $a and $b point to the same array reference, so that pushing to one adds the value to the other.

Re: Updating arrays within hashes
by davido (Cardinal) on Feb 10, 2011 at 10:18 UTC

    Though this isn't your actual problem (and we're actually not given enough of a snippet to see your problem), there is an efficiency issue that can be improved upon. If @prevarray changes infrequently, why not turn it into the keys of a hash, so that you can do hash lookups instead of grep?

    Your code snippet doesn't show where @prevarray comes from, but it's safe to assume based on your snippet that it's generated before your foreach loop. Since you are grepping once for each loop iteration, storing @prevarray as hash keys would eliminate the "loop within a loop" that a grep within a foreach loop creates.

    As for your question; there's no way that the logic you've shown in your snippet (your if(){}else{} statement) could produce the results you're describing unless there's a problem with your references. I think it's more likely that your snippet isn't an exact cut-and-paste of the code that's giving you trouble.


    Dave

      thanks for the reply. Apologies if this isn't clear I tried to keep it concise as possible so not to overload with code. This wasn't directly from my code so will paste a larger part see if it makes sense. The code is admittidely very ugly but its doing a very specific job. The @sequences is a list of two strings separated by a tab defined earlier still in the programme. @searchmotif is a short list of things to search for. I don't think I need to split the $temp3[0] into an array can just pattern match it but thought I'd leave this as it was written to see if it is clearer The larger bit of code is:

      foreach my $line4 (@sequences) {my @temp3 = split ( '\t', $line4); my $counter = 0; my $paa = chop ($temp3[0]); my @fouraas = split ( '', $temp3[0]); if ($paa =~ /s/i ) {$pStotal++; foreach my $key (@searchmotif) {unless ( my @stemp = grep ( /$key/, @ +fouraas)){ push (@{$nsaminoacids{$key}},$temp3[1]);next;} #closes if +loop else { push (@{$saminoacids{$key}},$temp3[1]); next;} #closes if +loop } #close foreach loop } #closes if S loop elsif ($paa=~ /t/i ) {$pTtotal++; foreach my $key (@searchmotif) {if (my @ttemp = grep ( /$key/, @foura +as)){ push (@{$taminoacids{$key}},$temp3[1]); } #close +s if loop else { push (@{$ntaminoacid +s{$key}},$temp3[1]); } #close +s if loop } #close while loop } #closes if T loop elsif ($paa =~ /y/i ){$pYtotal++; foreach my $key (@searchmotif) {if (my @ytemp = grep ( /$key/, @foura +as)){ push (@{$yaminoacids{$key}},$temp3[1]); } #close +s if loop else { push (@{$nyaminoacids{$k +ey}},$temp3[1]); } #close +s if loop } #close while loop } #closes if Y loop else {push (@error, "$line4\tNot recognised p amino acid\n"); } } #closes foreach loop

      i really don't understand why its not only using one of the actions after the unless command. This whole section if almost affectively just 3 repeats of what I posted ealier.

Re: Updating arrays within hashes
by roboticus (Chancellor) on Feb 10, 2011 at 14:39 UTC

    jonnyw83:

    You're already working with people on some answers, so this post is just a couple observations about your code.

    First, I'm wondering if using a pair of hashes with identical keys is a good idea. It can be, but I can't tell without knowing more about your program. I tend to use a single hash, and add a level to handle the split. For example, if you're comparing the contents of a pair of files, rather than having a hash for each file, you could add keys (such as FILE1 and FILE2) for the different files after your primary key:

    for my $key (keys %hash) { if (exists $hash{$key}{FILE1}) { if (exists $hash{$key}{FILE2}) { # Key exists in both files } else { # Key exists only in file 1 } } else { # Key exists only in file 2 } }

    Second: if you choose a different indentation style, you wouldn't need the comments that tell you '#closes if loop' -- good use of whitespace can make it obvious. There are quite a few different indentation styles that can make it clear. For example, you can vertically align the closing curly brace with the structure that's being closed:

    foreach my $key (keys (%hasha)) { if (my @temp = grep( /$key/, @prevarray ) ) { push(@{$hasha{$key}},$value); next; } else { push (@{$hashb{$key}},$value); next; } }

    Another popular method is to vertically align the open and closing curly braces, either indented:

    foreach my $key (keys (%hasha)) { if (my @temp = grep( /$key/, @prevarray ) ) { push(@{$hasha{$key}},$value); next; } else { push (@{$hashb{$key}},$value); next; } }

    or not:

    foreach my $key (keys (%hasha)) { if (my @temp = grep( /$key/, @prevarray ) ) { push(@{$hasha{$key}},$value); next; } else { push (@{$hashb{$key}},$value); next; } }

    There are many variations, and plenty of religious wars over indentation style. Don't get caught up in the wars, just be consistent within each script. Most people can easily get used to any of the styles fairly quickly, so don't get too hung up on it.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      Hi. I usually do keep everthing aligned in my editor for some reason it kept misaligning when copy and pasting it in to the web browser, hence I added the comments for clarity. Probably should have just re-aligned them.... Thanks for the tip on the different keys within a single array i hadn't thought of that. May actually be easier to restructure my programme that way. Although how to add elements to an array hash based on the outcome of a logical statement is still something I'll want to do. I've looked at some of the PERL documentation and couldn't find and examples of this to clarify. There's plenty on data structure but I couldn't find much on altering arrays within hashes to clarify why this is going wrong. Will attempt to sort it and post again if still struggling.
Re: Updating arrays within hashes
by goibhniu (Hermit) on Feb 10, 2011 at 16:05 UTC

    Others have helped you more directly; I thought I'd point out that the Perl Data Structures Cookbook has always been very useful for me in keeping complex data structures straight.


    #my sig used to say 'I humbly seek wisdom. '. Now it says:
    use strict;
    use warnings;
    I humbly seek wisdom.
Re: Updating arrays within hashes
by Anonymous Monk on Feb 10, 2011 at 18:06 UTC

    You are probably adding the same reference to both hashes, or two references to the same object.   Thus, changes to either one will affect the underlying target, which is the same in both cases.

    (May we now have a show-of-hands of everyone who has done that before?   Let me finish typing this so I can stick my own hand into the air ...)