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.


In reply to &lt;=&gt; - not only for sort anymore by Corion

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.