Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

<=> - not only for sort anymore

by Corion (Pope)
on Apr 11, 2001 at 13:53 UTC ( #71615=snippet: print w/replies, xml ) Need Help??
Description:

In my wizard-style CGI for a small website that might even go online, I print a tristate progress indicator showing which stages of the process the user already has completed, which stage is current and which stages remain. This was done with two ugly if statements and this morning in the subway a better solution hit me.

First, the initial solution, useable only for numbers :

my $pos = 3;
my $i;

foreach $i (1..5) {
  print( ("*"," ","|")[ $i <=> $pos ], " $i\n");
};

It uses the feature of Perl that a negative array index means "count from the end of the array", it prints | $i for completed stages, * $i for the current stage and $i for stages that haven't been completed yet.

Now, this is all good and nice, but my stages aren't numbered but they have names (the order of stages changes from time to time), and to worse the matter, the stage names are not sorted alphabetically (having a stage of Welcome would limit your available stage space quite a bit :-)). So the obvious application of the cmp operator fails miserably :

my @list = ("foo", "bar", "baz", "toto", "tata");
$pos = "baz";
foreach $i (@list) {
  print( ("*"," ","|")[ $i cmp $pos ], " $i\n") if $print;
};

The obvious fix is, of course, to determine the index of $pos in @list and then use $index as in the first case :

my $index = 0;
foreach (@list) {
  last if $pos eq $_;
  $index++;
};

foreach $i (0..$#list) {
  print( ("*"," ","|")[ $i <=> $index ], " $list[$i]\n");
};

This one works nice and well, but the multiline extra step of setting up $index is ugly (at least to my eyes). If we are not afraid to use Perl idioms that can degrade badly with large elements (and remember, this is a UI application, so no list presented to the user should exceed 10 elements (or a screenfull)), we can use the following one-liner to get at $index :

my $seen = 0;
$index = grep { $seen |= ($_ eq $pos); ! $seen;  } @list;

foreach $i (@list) {
  print( ("*"," ","|")[ 0 <=> $index-- ], " $i\n");
};

The above code is nice and well until @list contains many elements, as grep is forced to look at each and every element in @list. If you think that many stages are necessary to present to the user (bad idea) or if you want to use this in a non-interactive application, the following bad hack might speed things up, but I guess the behaviour shouldn't be relied upon :

# And now the hardcore, nonobvious solution, leaving the grep
# loop prematurely and discarding the result :
$index = 0;
grep { goto DONE if ($_ eq $pos); $index++; 0 } @list;
DONE:
foreach $i (@list) {
  print( ("*"," ","|")[ 0 <=> $index-- ], " $i\n");
};

1)

After thinking about these solutions, I came up with the final solution, which has almost no preparation steps and does the wanted thing :

# An inline function, that does not assume that $pos
# is in @list :
$seen = - $#list -1;
foreach $i (@list) {
  if ($i eq $pos) {$seen = 0} else { $seen++ };
  print( ("*"," ","|")[ $seen <=> 0 ], " $i\n");
};

Update :

1) As some casual browsing of the p5p archives pointed out to me, leaving a grep block via last is a bad (because undocumented to work) idea. perldoc -f next tells the same. So just use this in extreme situations where my last solution (or the no-setup-required solution presented by petral below) dosen't work.

Replies are listed 'Best First'.
Re: = - not only for sort anymore
by petral (Curate) on Apr 12, 2001 at 18:54 UTC
    Great idea (idiom, actually)!
    But, errr, here's a "no-setup" solution of your combined grep and index problem:
    print( ("|","*"," ")[ $seen && 2 || ($seen = $_ eq $pos) ], " $_\n" ) +for @list;


    p
      Nice use of <=>, but I think this might do the same thing even easier

          for $i (0..5) { print substr("|||||*",5-$i,5), "\n" }
        Oops, sorry. We were talking about names, not numbers. So how about this

           $seen=0;
           for $i (@list) { last if $i eq $pos; $seen++ }
           print substr("|||||*     ", 5-$seen, 5), "  $pos\n";
        
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: snippet [id://71615]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (2)
As of 2021-12-01 04:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?