Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Variant of map for special-casing the last item

by jdporter (Paladin)
on Oct 25, 2010 at 14:18 UTC ( [id://867243]=CUFP: print w/replies, xml ) Need Help??

This is a variant of map which makes it easy to special-case the last item in the list.

# usage: special_last_map { block } $sentinel_variable, @list_of_value +s; # You should examine the value of the sentinel variable inside your co +de block. # It will be True for the last item in the list; False otherwise. sub special_last_map(&\$@) { my $code = shift; my $is_last_sr = shift; my $n = $#_; map { $$is_last_sr = $_ == $n; local $_ = $_[$_]; &$code } 0 .. $#_ }

For example, say you're stuffing a list of strings into an html list, and you want to add an attribute to the final <li> element:

my $is_last; my @list_in_html = special_last_map { my $attr = $is_last ? ' class="last"' : ''; "<li$attr>$_</li>" } $is_last, @list;

Update: Ok, here's a version which makes checking for first and last items easy; and it uses $a and $b so that you don't have to provide any special variables.

sub map_with_index(&@) { my $code = shift; my $n = $#_; map { local $a = $_; local $b = $n - $_; local $_ = $_[$_]; &$code } 0 .. $#_ }
Example:
print for map_with_index { my $class = ( $a == 0 ? 'first' : '' ) . ( $b == 0 ? 'last' : '' +); my $attr = $class ? qq( class="$class") : ''; "<li$attr>$_</li>\n" } qw( alpha beta gamma delta );
What is the sound of Windows? Is it not the sound of a wall upon which people have smashed their heads... all the way through?

Replies are listed 'Best First'.
Re: Variant of map for special-casing the last item
by kcott (Archbishop) on Oct 26, 2010 at 01:29 UTC

    I probably would have this in 5.8:

    $ cat sentinel_58.pl #!perl use strict; use warnings; my @in = qw{start middle end}; my $i = 0; print map { join($_, ($i++ == $#in ? q{<li class="last">} : q{<li>}), q{</li>} +); } @in; $ sentinel_58.pl <li>start</li><li>middle</li><li class="last">end</li>

    And here's much the same offering for newer Perl versions:

    $ cat sentinel_512.pl #!perl use 5.12.0; use warnings; my @in = qw{start middle end}; say map { state $i = 0; join($_, ($i++ == $#in ? q{<li class="last">} : q{<li>}), q{</li>} +); } @in; $ sentinel_512.pl <li>start</li><li>middle</li><li class="last">end</li>

    -- Ken

      I definitely like the use of a state variable way more than the 5.8 approach. But what I still don't like about this and similar solutions is that they introduce a dependency inside the code block on the value of $#in. What if the input list isn't in an array variable? That's right — you can store it in a temporary. Well, that's what my solution does for you, keeping that mess out of your code.

      What is the sound of Windows? Is it not the sound of a wall upon which people have smashed their heads... all the way through?

        I wrote the 5.12 code first then, realising many people haven't moved past 5.8 yet, wrote the second version - I probably should have posted them in that order. Being able to confine the scope of a variable to a map (or similar) block via state in this way is a nice feature.

        I agree, this solution is not directly applicable to situations like map {...} qw{...}.

        -- Ken

Re: Variant of map for special-casing the last item
by Anonymous Monk on Oct 25, 2010 at 14:58 UTC
    Hmm, tricky. It appears overly verbose and specific
    { my @list_in_html = map { "<li$_</li>" } @list; $list_in_html[-1] =~ s!<li>!<li class="last">!; } { my @list_in_html = map { "<li$_</li>" } @list[0.. @list - 2]; push @list_in_html, qq!<li class="last">$list[-1]</li>!; }

      Your solutions look more verbose, and considerably more error-prone.

      Anyway, sometimes you just really want to do everything in a single map.

        Your solutions look more verbose, and considerably more error-prone.

        How is less characters more verbose and "considerably" more error-prone?

Re: Variant of map for special-casing the last item
by Anonymous Monk on Oct 25, 2010 at 15:22 UTC
    Something like this is a bit more generic
    my @list_in_html = map_by_index [ sub { "<li>$_</li>" }, last => sub { qq!<li class="last">$_</li>! }, ], @list; my @list_in_html = map_by_index [ sub { "<li>$_</li>" }, $#list => sub { qq!<li class="last">$_</li>! }, ], @list;
    where the first sub is the default, and last (or $#list) is applied only for the last element.

    Or using List::MapList (which could be optimized)

    my @list_in_html = maplist( [ ( sub { "<li>$_</li>" } ) x ( @list - 1 ), sub { qq!<li class="last">$_</li>! }, ], @list );

      The maplist solution looks really sweet. And it's obviously far more generic than mine. But I have a little problem with the fact that it makes an array (of subrefs) equal in size to the input list. This could be quite expensive. Maybe it should be able to take a "sparse array" (i.e. hash) as an alternative...

      Where does that map_by_index come from?

      What is the sound of Windows? Is it not the sound of a wall upon which people have smashed their heads... all the way through?
        It doesn't actually exist :) but hopefully you see how/what it would do :)
Re: Variant of map for special-casing the last item
by hexcoder (Curate) on Nov 03, 2010 at 22:28 UTC
    Thanks for the puzzle. Has the following code any shortcomings?
    use strict; use warnings; sub first_last_map(&&&@) { my $code = shift; my $first_code = shift; my $last_code = shift; ($first_code->($_[0]), (map { $code->($_) } @_[ 1 .. $#_ - 1]), $last_code->($_[-1]) ) } print for first_last_map( sub { "<li>$_[0]</li>\n" }, sub { "<li class=\"first\">$_[0]</li>\n" }, sub { "<li class=\"last\">$_[0]</li>\n" }, qw( alpha beta gamma delta ));
    I am not really happy with the sub { ... } arguments. I would like to be able to give multiple blocks instead:
    print for first_last_map( { "<li>$_[0]</li>\n" }, { "<li class=\"first\">$_[0]</li>\n" }, { "<li class=\"last\">$_[0]</li>\n" }, qw( alpha beta gamma delta ));
    But written as above they are parsed as hash constructors :-(. Is there a coercion I could use instead?

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (3)
As of 2024-04-24 18:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found