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

Dear Monks,

I am currently writing a CGI application and have a few lines of code which slow the program down (so it stops) when included. I wondered how I could re-write it so it doesn't cause this problem.

The core of the program is within a foreach ($line) loop so certain variables are'nt defined for each line - I thought I would have got around this problem by using an if-defined test. I would appreciate your advice.

# @array and @numbers both contain numbers. if (defined ($array[0])) { foreach my $num (@numbers) { if ($array[0] == $num) { print "FOUND<P>"; } } }

Replies are listed 'Best First'.
Re: slow CGI's
by thinker (Parson) on Jul 07, 2003 at 11:18 UTC
    Hi AM,

    Using a hash slice should do what you want.
    Try this
    #!/usr/bin/perl -w + use strict; + my @array = (1, 3, 5, 7, 9); my @numbers = (1, 4, 6 , 8); + my %nums; + # @nums{ @numbers } = 1; # incorrect @nums{ @numbers } = (1) x @numbers; # as jryan points out + print "FOUND\n" if $nums{ $array[0] };

    Hope this helps

    thinker

    Update : corrected my error. thanks jryan

      I think you mean:

      @nums{ @numbers } = (1) x @numbers;

      With your example, you were assigning a 1-element list (the hash slice forces the rhs into list context) to the hash slice, and so only one item of the hash assigned to will have a true value. For instance:

      #!/usr/bin/perl -w use strict; use Data::Dumper; my @array = (1, 3, 5, 7, 9); my @numbers = (1, 4, 6 , 8); my(%nums, %rums); @nums{ @numbers } = 1; @rums{ @numbers } = (1) x @numbers; print Dumper \%nums; print Dumper \%rums;

      Yields the output:

      $VAR1 = { '8' => undef, '1' => 1, '4' => undef, '6' => undef }; $VAR1 = { '8' => 1, '1' => 1, '4' => 1, '6' => 1 };

      Which will, of course, lead to unexpected results if the item you are checking doesn't happen to be the one that the true value was assigned to.

      On the other hand, you could always change the last two lines of your example to this instead:

      @nums{ @numbers } = (); print "FOUND\n" if exists $nums{ $array[0] };

      It's more efficient, at any rate.

Re: slow CGI's
by bwana147 (Pilgrim) on Jul 07, 2003 at 15:27 UTC

    I don't know to what extent Perl is optimised, but I suppose accessing a member of an array is somewhat slower than accessing a private variable. And especially if the indexing takes place inside a loop. So how about this:

    if ( defined($array[0]) ) { my $array0 = $array[0]; foreach ( @numbers ) { next unless $array0 == $_; print "FOUND<p>"; } }

    --bwana147

      I downvoted this node because I think it is an inappropriate suggestion. Rule #1 of benchmarking is don't guess. Rule #2 of benchmarking is optimize what really matters.

      The biggest potential problem I can see with this code (without seeing the data set) is that the loop tends to O(n/2) performance. If there are a lot of numbers in the array, it will take a while to find the right number. A hash lookup, tending to O(1) performance, would be much more performant.

        Interestingly, benchmarks do not bear this assumption out. (perl 5.8.0 on a celeron lots of other stuff going on at the same time, but tried multiple times, very similar results).
        #!/usr/local/bin/perl use Benchmark; for (1.. 100000) { $rand = rand * 100; push @rvs, $rand; $last = $rand; } $array[0] = $rand; timethese ( 100, {grepit => \&grepit, hashit => \&hashit}); sub grepit { return 1 if (grep /$array[0]/, @rvs); } sub hashit { @hash{@rvs} = (1) x @rvs; return 1 if ($hash{$array[0]}); }
        Benchmark: timing 100 iterations of grepit, hashit... grepit: 19 wallclock secs (19.36 usr + 0.03 sys = 19.39 CPU) @ 5.16/s (n=100) hashit: 28 wallclock secs (27.84 usr + 0.04 sys = 27.88 CPU) @ 3.59/s (n=100) the number is 0.359661571026372 Grepit found the number Hashit found the number