Search code examples
perlhashmapperl-data-structures

Perl: do-while loop over number of keys in a "growing" Hash of Arrays produces "extra" output


I am not sure if I have made it perfectly clear in the title, but my problem is as such: I have do-while loop that takes an initialized hash of arrays (HoA), computes a new dimensional array, and then compares the generated array with the array associated with the array's of each key (for simplicity the keys are numeric...so maybe better to just do this as an array of arrays). If certain crietria are fit (e.g. the new values are within a particular "distance") a new key is generated for the HoA and the generated array is added to the HoAs with the new key.

What is strange is that I limit the number of new keys with the do-while loop, and yet sometimes when I run the code (no changes made at all), I get "extra" keys.

My code is below and any help would be great.

use strict;
use warnings;
use Data::Dumper;

my $PolymerSize=6;

# each monomer/node can bond upto 3 times
my $maxNeighbors=3;

#Store coordinates and number of neighbors for each  monomer/node
# (in C would be an array of structs)

my %HoA;
my %BondHoA; #who are my neighbors
my @coordsNeighbors;
my $element; #Iteration dummy variable
my %dist; #temporary distance hash
my $return_flag;

$coordsNeighbors[0]=0; #Xcoord
$coordsNeighbors[1]=0; #YCoord
$coordsNeighbors[2]=0; #ZCoord
$coordsNeighbors[3]=0; #How many bonded neighbors?

#Intialize origin (first node/monomer)
push @{$HoA{0}}, $coordsNeighbors[0];
push @{$HoA{0}}, $coordsNeighbors[1];
push @{$HoA{0}}, $coordsNeighbors[2];
push @{$HoA{0}}, $coordsNeighbors[3];

#Generate new nodes/monomer and "grow" polymer
do{
    for(my $j=0;$j<3;$j++){
        #generate coords of potent. monomers/node
        $coordsNeighbors[$j] = int($PolymerSize*rand());
    }

    $coordsNeighbors[3]=0;

    #loop through existing monomers/nodes
    foreach $element ( keys %HoA) {
        #if this monomer doesn't have the max bonds proceed
        if( ($HoA{$element}[3])!=$maxNeighbors) {
            my $tempx=$HoA{$element}[0]-$coordsNeighbors[0];
            my $tempy=$HoA{$element}[1]-$coordsNeighbors[1];
            my $tempz=$HoA{$element}[2]-$coordsNeighbors[2];

            #make hash of L1 distances
            $dist{$element} .=abs($tempx)+abs($tempy)+abs($tempz);
        }
    }

    #check if any distance is != 1; no-bond could be made if so
    foreach(keys %dist){
        if($dist{$_}!=1) {
            delete $dist{$_};
        }
    }

    #potential monomer is good, so add to HoA and update bonds
    foreach $element (keys %dist){
        $HoA{$element}[3]++;
        my $newKey=scalar (keys %HoA);
        if($newKey!=($PolymerSize-1)){
            push @{$HoA{$newKey}}, $coordsNeighbors[0];
            push @{$HoA{$newKey}}, $coordsNeighbors[1];
            push @{$HoA{$newKey}}, $coordsNeighbors[2];
            push @{$HoA{$newKey}}, $coordsNeighbors[3]+1;
            push @{$BondHoA{$element}}, "$newKey";
            push @{$BondHoA{$newKey}}, "$element";
        }
        delete $dist{$element};
    }

} while((keys %HoA)<$PolymerSize-1);

foreach $element (keys %HoA) {
    print "$element \t $HoA{$element}[0] \t $HoA{$element}[1] \t $HoA{$element}[2]\n";
}

The general idea behind this code is to do something like grow a polymer like a DLA (diffusion-limited aggregate) in 3D, so two things need to be right for it to work:

1) get the right number of monomers (HoA keys noted above).

2) Make sure there are no monomer-overlaps (no L1 distances of 0 (manhanttan distance, since we are on a grid)).

[EDIT] I apparently forgot to include the desired output (my apologizes).

The output should be something like:

0   0  0  0
1   1  0  0
2   0  1  0
3   2  0  0
4   2  1  0
5   2  2  0

But I end up getting something like:

0   0   0  0
1   1   0  0
2   0   1  0
3   0   0  0 
4   1   1  0
5   0   1  1
6   2   0  0

(and some times even an 8th or 9th value)


Solution

  • Following the advice of @Schwern I went back and looked at the

    foreach $element (keys %dist){ ...
    

    loop and realized that indeed what was happening was that even after deleting all of keys that coincide with non-one values in the %dist hash, I could have more than 1 member in the %dist hash. A simple fix was then to add a "last"

    foreach $element (keys %dist){
        $HoA{$element}[3]++;
        my $newKey=scalar (keys %HoA);
        if($newKey!=($PolymerSize-1)){
            push @{$HoA{$newKey}}, $coordsNeighbors[0];
            push @{$HoA{$newKey}}, $coordsNeighbors[1];
            push @{$HoA{$newKey}}, $coordsNeighbors[2];
            push @{$HoA{$newKey}}, $coordsNeighbors[3]+1;
            push @{$BondHoA{$element}}, "$newKey";
            push @{$BondHoA{$newKey}}, "$element";
           #here I add a last to break out and go to the next do-while iter.
            last
        }
    

    And then add to the top of the do-while loop a call to undef(%dist), to clear it for use again.

    Also after fixing this bug I went back and cleaned-up most of the code, and it is now provided below for those interested:

    #!/usr/bin/perl;
    use strict;
    use warnings;
    use Data::Dumper;
    my $PolymerSize=8;
    my $maxNeighbors=3; #each monomer/node can bond upto 3 times
    my %HoA; #Store coordinates and number of neighbors for each monomer/node (in C would be an array of structs)
    my %BondHoA; #who are my neighbors
    my @coordsNeighbors;
    my $element; #Iteration dummy variable
    my %dist; #temporary distance hash
    my $selected;
    $coordsNeighbors[0]=0; #Xcoord
    $coordsNeighbors[1]=0; #YCoord
    $coordsNeighbors[2]=0; #ZCoord
    $coordsNeighbors[3]=$maxNeighbors; #How many bonded neighbors?
    my $tempx;
    my $tempy;
    my $tempz;
    my $tempL1;
    #Intialize origin (first node/monomer)
    push @{$HoA{0}}, $coordsNeighbors[0];
    push @{$HoA{0}}, $coordsNeighbors[1];
    push @{$HoA{0}}, $coordsNeighbors[2];
    push @{$HoA{0}}, $coordsNeighbors[3];
    my $iter=0;
    #Generate new nodes/monomer and "grow" polymer
    do{
     #print "$iter\n";
     # $iter++;
      undef(%dist);
            for(my $j=0;$j<3;$j++){
                 $coordsNeighbors[$j]=int($PolymerSize*rand()); #generate coords of potent. monomers/node
             }
                 $coordsNeighbors[3]=$maxNeighbors;
            foreach $element ( keys %HoA) { #loop through existing monomers/nodes
                    $tempx=int($HoA{$element}[0])-int($coordsNeighbors[0]);
                    $tempy=int($HoA{$element}[1])-int($coordsNeighbors[1]);
                    $tempz=int($HoA{$element}[2])-int($coordsNeighbors[2]);
                    $tempL1=abs($tempx)+abs($tempy)+abs($tempz);
                      if($tempL1==1){
                            $dist{$element} .= $tempL1;
                      } elsif($tempL1==0){
                            undef(%dist);
                            last;
                      }
             }
            foreach $element (keys %dist){
                     if ( ($HoA{$element}[3]>0)){ #potential monomer is good, so add to HoA and update bonds
                            my $newKey=scalar (keys %HoA);
                            if($newKey<=($PolymerSize-1)){
                             $HoA{$element}[3]--;
                             push @{$HoA{$newKey}}, $coordsNeighbors[0];
                             push @{$HoA{$newKey}}, $coordsNeighbors[1];
                             push @{$HoA{$newKey}}, $coordsNeighbors[2];
                             push @{$HoA{$newKey}}, $coordsNeighbors[3];
                             push @{$BondHoA{$element}}, "$newKey";
                             push @{$BondHoA{$newKey}}, "$element";
                             last;
                            }
                     }
                  }
    } while((scalar (keys %HoA))<=$PolymerSize-1);
    foreach $element (keys %HoA){
            print "$element \t $HoA{$element}[0] \t $HoA{$element}[1] \t $HoA{$element}[2]\n";
    }