neemie
neemie

Reputation: 83

Running a nested while loop inside a foreach loop in Perl

I'm trying to use a foreach loop to loop through an array and then use a nested while loop to loop through each line of a text file to see if the array element matches a line of text; if so then I push data from that line into a new array to perform calculations.

The outer foreach loop appears to be working correctly (based on printed results with each array element) but the inner while loop is not looping (same data pushed into array each time).

Any advice?

The code is below

#! /usr/bin/perl -T

use CGI qw(:cgi-lib :standard);

print "Content-type: text/html\n\n";

my $input       = param('sequence');
my $meanexpfile = "final_expression_complete.txt";

open(FILE, $meanexpfile) or print "unable to open file";

my @meanmatches;

@regex = (split /\s/, $input);

foreach $regex (@regex) {

    while (my $line = <FILE>) {
        if ( $line =~ m/$regex\s(.+\n)/i ) {
            push(@meanmatches, $1);
        }
    }

    my $average                  = average(@meanmatches);
    my $std_dev                  = std_dev($average, @meanmatches);
    my $average_round            = sprintf("%0.4f", $average);
    my $stdev_round              = sprintf("%0.4f", $std_dev);
    my $coefficient_of_variation = $stdev_round / $average_round;
    my $cv_round                 = sprintf("%0.4f", $coefficient_of_variation);

    print font(
        { color => "blue" }, "<br><B>$regex average: $average_round   
&nbspStandard deviation: $stdev_round&nbspCoefficient of 
variation(Cv): $cv_round</B>"
    );
}

sub average {
    my (@values) = @_;
    my $count    = scalar @values;
    my $total    = 0;

    $total += $_ for @values;

    return $count ? $total / $count : 0;
}

sub std_dev {
    my ($average, @values) = @_;
    my $count       = scalar @values;
    my $std_dev_sum = 0;

    $std_dev_sum += ($_ - $average)**2 for @values;

    return $count ? sqrt($std_dev_sum / $count) : 0;
}

Upvotes: 0

Views: 2852

Answers (2)

Sobrique
Sobrique

Reputation: 53478

Yes, my advice would be:

  • Turn on strict and warnings.
  • perltidy your code,
  • use 3 argument open: open ( my $inputfile, "<", 'final_expression.txt' );
  • die if it doesn't open - the rest of your program is irrelevant.
  • chomp $line
  • you are iterating your filehandle, but once you've done this you're at the end of file for the next iteration of the foreach loop so your while loops becomes a null operation. Simplistically, reading the file into an array my @lines = <FILE>; would fix this.

So with that in mind:

#!/usr/bin/perl -T

use strict;
use warnings;

use CGI qw(:cgi-lib :standard);
print "Content-type: text/html\n\n";

my $input       = param('sequence');
my $meanexpfile = "final_expression_complete.txt";
open( my $input_file, "<", $meanexpfile ) or die "unable to open file";
my @meanmatches;

my @regex = ( split /\s/, $input );
my @lines = <$input_file>;
chomp (@lines);
close($input_file) or warn $!;

foreach my $regex (@regex) {
    foreach my $line (@lines) {
        if ( $line =~ m/$regex\s(.+\n)/i ) {
            push( @meanmatches, $1 );
        }
    }

    my $average                  = average(@meanmatches);
    my $std_dev                  = std_dev( $average, @meanmatches );
    my $average_round            = sprintf( "%0.4f", $average );
    my $stdev_round              = sprintf( "%0.4f", $std_dev );
    my $coefficient_of_variation = $stdev_round / $average_round;
    my $cv_round = sprintf( "%0.4f", $coefficient_of_variation );

    print font(
        { color => "blue" }, "<br><B>$regex average: $average_round   
&nbspStandard deviation: $stdev_round&nbspCoefficient of 
variation(Cv): $cv_round</B>"
    );
}

sub average {
    my (@values) = @_;
    my $count    = scalar @values;
    my $total    = 0;
    $total += $_ for @values;

    return $count ? $total / $count : 0;
}

sub std_dev {
    my ( $average, @values ) = @_;

    my $count       = scalar @values;
    my $std_dev_sum = 0;
    $std_dev_sum += ( $_ - $average )**2 for @values;

    return $count ? sqrt( $std_dev_sum / $count ) : 0;
}

Upvotes: 1

Andrei
Andrei

Reputation: 56688

The problem here is that starting from the second iteration of foreach you are trying to read from already read file handle. You need to rewind to the beginning to read it again:

foreach $regex (@regex) {
    seek FILE, 0, 0;
    while ( my $line = <FILE> ) {

However that does not look very performant. Why read file several times at all, when you can read it once before the foreach starts, and then iterate through the list:

my @lines;
while (<FILE>) {
    push (@lines, $_);
}

foreach $regex (@regex) {
    foreach $line (@lines) {

Having the latter, you might also what to consider using grep instead of the while loop.

Upvotes: 0

Related Questions