Survey of $#array Usage

In October 2001 I surveyed uses of $#array in about 3.5 months of articles from comp.lang.perl.misc. I did this because I believed that $#array was frequently misused and was therefore a 'red flag'.

My results did not indicate that $#array was a red flag in the sense of being 'almost always wrong'. About 19% of uses of $#array were inappropriate. Typical programmers will probably get a positive payoff by stopping briefly after each $#array to consider whether it shouldn't be replaced as described below.


Inappropriate Uses of $#array

badfor

        for ($i=0; $i<= $#array; $i++) {
          # do something with $array[$i]
          # never use $i except as part of $array[$i]
        }

This is almost always clearer as:

        for my $element (@array) {
          # do something with $element
        }

badlength

Here $#array is used to compute the length of an array for some sort of check or comparison on the number of elements in the array. Often, @array leads to clearer code. For example:

        $#ARGV <= 0 or die "Usage:\n\t$0 [logfile]\n";

This would be clearer as:

        @ARGV <= 1 or die "Usage:\n\t$0 [logfile]\n";
because the intent is to check for at most one argument. (The original code that this example was based on got the test wrong.) Similarly, people sometimes write $n_elements = $#array + 1 when it would be simpler to write $n_elements = @array.

badlastelt

$#array is sometimes used to generate the last index of @array so that the last element can be extracted with $array[$#array]. It is almost always preferable to use $array[-1].

Overview of Results

The original sample contained 9037 articles. 173 of these articles contained the sequence $#. I examined all 173 articles and found about 192 occurrences of $#.

Not all of these were instances of $#array. Some were coincidental (included below under nocode) or appeared in discussions of the deprecated $# ($OFMT) variable. I disregarded these.

Of the remainder, many appeared in the obfuscated signatures of Damian James, Jasper McCrea, Abigail, and others. I disregarded these. I also disregarded appearances of $#array in code quoted from previous articles.

I analyzed a total of 84 occurrences of $#array. The uses broke down as follows:

     indices  50 60%
      badfor   9 11%
       bound   9 11%
  pre-extend   7  8%
   badlength   6  7%
  badlastelt   1  1%
    instring   1  1%
      length   1  1%

 obfuscation  53
      quoted  32
      nocode  18
        ofmt   5

The inappropriate uses (badfor, badlength, and badlastelt) constituted 19% of the total uses of $#array.

The results are biased because of the presence of two or three large threads that mentioned the same code repeatedly. For example, there was an extensive discussion of

        my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_] ? "->$_[$_]<-" : '*UNDEF*'), 0 .. $#_;

This discussion alone contributed 14/50 in the indices category.

These large, repetitive discussions tended to contribute appropriate uses of $#array. I believe that if they were corrected for, it would appear that at least 30% of uses of $#array were inappropriate.

Explanation of categorizations

badfor

means for($i=0; $i<=$#array; $i++) { ... $a[$i] ... } that would have been better if it had been converted to for $elt (@array) ( ... $elt ... )

badlastelt

means that $#array was used in the construction $array[$#array] to access the last element. $array[-1] would have been preferable.

badlength

means that $#array was used to get the length of the array for a check or comparison, and @array would have yielded clearer code.

bound

means that $#array was used as an upper bound in a check on a variable that was about to be used to index @array. Exception: Cases like for ($i=0; $i<=$#array; $i++), where the bound is on an array index variable that loops over all indices of an array, are categorized under indices or badfor.

indices

means for (0 .. $#array) where it was not possible to use for $elt (@array) instead.

instring

means that $#array was interpolated into a string to indicate an array length. This use is marginal, because the only example I found was erroneous. I counted it as an appropriate use because the error was probably unimportant.

nocode

means that the poster did not show enough code to judge what was really going on, or was talking about the use of $#array rather than actually using it, or the mention of $#array appeared in the code, but only in a comment.

obfuscation

means that the $#array appeared only as part of an explicitly obfuscated program, usually as part of a signature file. (Damian James, Jasper McCrea, Abigail, and Sean McAfee were the big contributors here.) When $#array appeared as part of a signature file, and there was another appearance of $#array not related to the signature, the appearance in the signature was disregarded.

ofmt

was a false hit. It was the deprecated $# variable that was being discussed, not the $#array construction.

pre-extend

means that the $#array notation was used as an lvalue to pre-extend the size of @array.

quoted

means that the $#array appeared only in quoted code from a previous article. When $#array appeared in quoted code, and there was another appearance of $#array, the appearance in the quoted code was disregarded.

Categorization of Each Appearance of $#

Each line represents one appearance of the string $#. The numbers are article numbers. Each is linked to the complete text of the article. If the article number is annotated with a *, that indicates that I have additional comments about the code in the article. Each appearance of $# is annotated with the categorization I assigned to it, and, where relevant, a brief extract of the original article.

0051   badfor       for ($i=0; $i<=$#scores; $i++){ 
0053   quoted       
0054   quoted       
0056   nocode       This should be $#data, the array you loop over, 
0057   quoted       
0058   quoted       
0060   nocode       "$ra[ index that is > $#ra ]" evaluates to undef.
0100   indices      foreach $i (0 .. $#parts) {       # dump each part...
0176   indices      @sorted_index = sort{ $game[$a]{'score'} <=> $gam...
0457   badfor       for(my $a = 0; $a <= $#contents; $a++) {
0458   quoted       
0554   indices      for ($a = 0; $a <= $#SomeArray; $a++)
0555   indices      for( my $a = 0; $a <= $#some_array; $a++ ){
0569   obfuscation  
0581   indices      for my $a ( 0 .. $#SomeArray ) {
0581   indices      for my $a ( 0 .. $#SomeArray ) {
0641   badlength    $#ARGV >= 1 or die "Usage:\n\t$0 [logfile]\n";
0687   badfor       foreach $i (0 .. $#in)
0723   quoted       
0767   quoted       
0818   badlength    $total = $total / ($#spectrum + 1);
0823   quoted       
0843   quoted       
0847   quoted       
0927   nocode       %$#@! typos.
0956   badfor       foreach my $i (0 .. $#site) {
1146   indices      $hash{$keys[$_]} = $vals[$_] for 0..$#keys;
1168   indices      while($index <= $#foo)
1170   indices      @array_index{@keys}=(0..$#keys);
1171   indices      my($subscript) = grep { $item eq $foo[$_]} 0 .. $#foo;
1172   quoted       
1173   indices      foreach my $index (0 .. $#foo) {
1174   indices      for (0..$#foo) {
1352 * indices      $element_count = $#Array; ... for ($iterate = 0; $itera...
1354 * indices      $element_count = $#Array; ... for ($iterate = 0; $itera...
1377   indices      foreach my $index (0..$#info) {
1379   badlength    if ($#{ $non_null } > -1)
1380   quoted       
1490   bound        if ($#list > $last_item + $args{max_display}) {
1490   bound        if ($last_item >= $#list) {
1490   indices      $last_item = $#list;
1490   indices      $last_item = $#list; ...         foreach ($first_item ....
1532   badlastelt   my ($alias, $email, $bid, $time, $add1, $add2, $add3) =...
1634   quoted       
1747   nocode       It should be either: for (my $i=0; $i <= $#temp; $i+...
1748   nocode       *grin*.  What does "$i <= $#temp" mean?  
1748   nocode       but how can I compare a numeric value to $#temp?? What ...
1749   nocode       $#temp is the last array index of @temp.  By using @tem...
1764   nocode       $#temp is equal to 5 [1]. $# is another (deprecated) va...
1826   nocode       I was referring to $#temp when I wrote $# - meaning the...
2047   indices      for( 0 .. $#children ) {
2162   badfor       for $i ( 0 .. $#abc ) {
2163   quoted       
2164   quoted       
2165   quoted       
2272   nocode       the line foreach my $n (0 .. $#connection) { ... does t...
2273   quoted       
2326   indices      @index = @index = sort {  $x[$nth1][$a] cmp $x[$nth1][$...
2326   indices      @index = sort {  $x[$nth][$a] cmp $x[$nth][$b] } (0..$#...
2491   quoted       
2587   badfor       for (0..$#files)
2588   quoted       
2650   quoted       
2712   bound        $hi = $#words if $hi > $#words;
2991   obfuscation  
2992   obfuscation  
3079   badfor       for my $r (0 .. $#records) {
3117   obfuscation  
3564   obfuscation  
3993   indices      foreach (0..$#list) {
4019 * indices      @seen{ map { $linkarray->[$_ & ~1] } 2 .. $#$lin...
4019 * indices      map { $la->[$_ & ~1] } 2 .. $#$la;
4027   obfuscation  
4056   obfuscation  
4091   obfuscation  
4109 * indices      map "\$a->{\$sortkeys[$_]} cmp \$b->{\$sortkeys[$...
4143   obfuscation  
4175   obfuscation  
4214   obfuscation  
4222   obfuscation  
4231   obfuscation  
4338   obfuscation  
4342   obfuscation  
4345   obfuscation  
4372   nocode       Your solution has the advantage over other solutions th...
4391   obfuscation  
4476   indices      for my $i ( 0 .. $#{ $chain[7] } ) {
4506   obfuscation  
4588   indices      print join ', ', map { "Arg$_ ->$_[$_]<-" } 0 .. ...
4615   obfuscation  
4798   nocode       # make use of $#array+1 == @array
4832   nocode       $#{$a[i][j]} #works
4832   nocode       $#{$a[i]}    #works
4832   nocode       $#{$a}       #DOES NOT WORK (I usually get -1)
4859   nocode       You might try $#a instead and it will work.
4889 * nocode       
4890   quoted       
4913   quoted       
4914   quoted       
4937 * length       print "$_: >@array1[map 1+2*$_, 0..($#array1-1)/2]&l...
4938   badfor       foreach my $b (0 .. $#array1) {
4947   obfuscation  
5083   indices      join ', ', map { "Arg$_ ->$_[$_]<-" } 0 .. $#_;
5122   indices      join ', ', map "Arg$_ " . ( defined $_[$_] ? "->$_[$...
5123   indices      print join ', ', map { "Arg$_ ->$_[$_]<-" } 0 .. ...
5123   indices      print join ', ', map {defined($_[$_]) ? "Arg$_ ->$_[...
5146   obfuscation  
5151   obfuscation  
5173   quoted       
5177   obfuscation  
5210 * pre-extend   $#a=0;
5212   indices      my $argtmp =  join ', ', map { "Arg$_ ->$_[$_]<-"...
5212   indices      my $argtmp = join ', ', map {defined($_[$_]) ? "Arg$_ -...
5212   indices      my $argtmp = join ', ', map {defined($_[$_]) ? "Arg$_ -...
5240   quoted       
5294   obfuscation  
5401   obfuscation  
5444   pre-extend   $#array = 1_000_000;  # a million elements
5477   indices      $array[$_] =~ /$val/ and splice @array, $_, 1 for rever...
5482   quoted       
5535   obfuscation  
5545   obfuscation  
5655   obfuscation  
5690   obfuscation  
5692   obfuscation  
5824   indices      foreach $line (@lines[1..$#lines]) {
5869   obfuscation  
5870   obfuscation  
5953   indices      my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_]...
5953   indices      my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_]...
5992   indices      my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_]...
5992   indices      my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_]...
6027   ofmt         
6028   ofmt         
6100   indices      my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_]...
6100   indices      my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_]...
6100   indices      my $argtmp = join ', ', map "Arg$_ " . ( defined $_[$_]...
6155   obfuscation  
6169   obfuscation  
6186   obfuscation  
6209   obfuscation  
6284   ofmt         
6320   obfuscation  
6413   ofmt         
6414   ofmt         
6709   bound        /(\d{4})/&& $1<=$#k && s/$1/$k[$1]/
6710   quoted       
6741   bound        /(\d{4})/&& $1<=$#k && s/$1/$k[$1]/;
6742   quoted       
6743   quoted       
6751   bound        for (my $i=0; $i <= $ips_shown && $i <= $...
6767   bound        /(\d{4})/&& $1<=$#k && ($a=$1,1) &am...
6789   bound        /(\d{4})/&& $1<=$#k && ($_=$`.$1.$')...
6790   bound        /(\d{4})/&& $1<=$#k && ($_=$`.$k[$1]...
6791   quoted       
6817   pre-extend   $#{$ptr->{big}}=5000000;
6949   pre-extend   $#{$ptr->{big}}=5000000;
6950   pre-extend   $#{$ptr->{big}}=5000000;
6984   pre-extend   $#{$ptr->{big}}=2500000;
6984   pre-extend   $#{$ptr->{big}}=5000000;
7114   obfuscation  
7311   obfuscation  
7341   obfuscation  
7484   obfuscation  
7580   obfuscation  
7603   indices      my @create = map "$names[$_] $types[$_]", 0..$#names;
7620   obfuscation  
7628   obfuscation  
7634   obfuscation  
7641   nocode       print "Index of last element in array: $#items\n";
7653   indices      while ($index <= $#dirs) {
7653   instring     print "$#dirs is the length of the array \n";
7658   quoted       
7746   obfuscation  
7782   badlength    shift @lines if $#lines > 5;
7968   indices      for my $i ( 0 .. $#words ) {
8197   obfuscation  
8227   obfuscation  
8272 * indices      for($r = 0;$r <= $#array;$r++)
8319   indices      my %line_num = map {$line_break[$_] => $_} 0..$#line...
8470   badfor       for my $i (0..$#data) {
8501   indices      for( my $i = $#names; $i >= 2; --$i ) {
8534   obfuscation  
8573   badlength    if ( $#ARGV == -1 )
8573   badlength    if ( $#tokenRegex < 0 )
8592   quoted       
8608   indices      @line_num{@line_break} = 0..$#line_break;
8698   indices      for my $i ( 0..$#data ) {
8742   obfuscation  
8743   obfuscation  
8823   obfuscation  
8920   obfuscation  

Return to: Universe of Discourse main page | Perl Paraphernalia | Classes and Talks | Program Repair Shop and Red Flags

mjd-perl-yak@plover.com