Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Fold a list using map splices

by hiseldl (Priest)
on Feb 19, 2003 at 20:28 UTC ( #236799=snippet: print w/replies, xml ) Need Help??
Description:

map typically returns the number of elements that are in your original list. How does map return fewer elements? --By using a test and upon failure return an empty list, "()" because undef, 0 (zero), or leaving off the empty list all create extra elements in the list.

I recently had a need to splice an array into 4 element array refs. Here's an example implementation:

# use CGI because it has subs that operate on array refs
use CGI qw/:standard/;

use Data::Dumper;
use strict;

my @list = (1..7);
print "list: ", Dumper(\@list),"\n";

# set the number of elements for the splices
my $num_elements = 4;


my @newlist =

    # This map operates on the list of splices.
    # If you remove the empty list "()" extra
    # empty elements will appear in your new list.
    #
    # CGI::Tr( $array_ref ) is used for demonstration purposes.
    map { Tr($_) }

    # This map operates on the spliced elements.
    #
    # CGI::td( $array_ref ) is used for demonstration purposes.
    map { td( [ splice(@list,0,$num_elements) ] ) }

    0 .. (@list/$num_elements);

print "newlist: ", Dumper(\@newlist),"\n";

__END__
    
>perl example.pl
list: $VAR1 = [
          1,
          2,
          3,
          4,
          5,
          6,
          7
        ];

newlist: $VAR1 = [
          '<tr><td>1</td> <td>2</td> <td>3</td> <td>4</td></tr>',
          '<tr><td>5</td> <td>6</td> <td>7</td></tr>'
        ];

Update: Changed the list from @_ to 0 .. (@_ / $num_elements) and removed some extra error checking from the top map. Thank you Aristotle for your suggestions.

$num_elements = 4;
@_ = ( list,of,elements,to,splice );

@spliced_list =
  # This map operates on the list of splices.
  # the left side of || should test something
  # most likely something in $_ and return
  # something (or call a sub) if the test is
  # successful, and if the test fails, it should
  # failover to the right side of the || which
  # returns ().  
  map { func1($_) }

  # This map operates on the spliced elements.
  # Splicing the list removes those elements
  # from the list as a side affect.
  map { func2( [ splice(@_, 0, $num_elements) ] ) }

  # Since this snippet shows how to fold an array,
  # we only need to iterate over the count of
  # new elements.
  0 .. (@_ / $num_elements);
Replies are listed 'Best First'.
Re: Fold a list using map splices
by Aristotle (Chancellor) on Feb 19, 2003 at 22:59 UTC
    Iterating your splicing map over the same array its block modifies as a side effect is a really dirty approach though; no wonder you have to test $_ to guard against its failures. The right way to do this is in two steps:
    my @spliced; push @spliced, [ splice @_, 0, $num_elements ] while @_; my @result = map func1($_), @spliced;
    or maybe the straightforward
    my @result; push @result, func1([ splice @_, 0, $num_elements ]) while @_;
    It's awkward in Perl 5, that's true. Perl 6 is going to do away with this limitation of the list iteration functions and operators.

    Makeshifts last the longest.

      I like the straightforward,

      push @result, func1([ splice @_, 0, $num_elements ]) while @_;
      but this expression could not be placed in a sub's parameter list because push does not return a list, rather it returns the number of new elements in the list.

      To clarify, this technique is used primarily to transform function parameters before they get passed. I could not use your two step method for that case. This technique is useful for creating HTML tables with N columns, where Tr() and td() both accept array refs. And, in order to fold the list without breaking stride, I used map.

      As a side note, I have been programming in Lisp for the last couple months, so I'll admit that this technique appears more functional than procedural. :-)

      --
      hiseldl
      What time is it? It's Camel Time!

      P.S. A matter of opinion:
      two steps is right? Just because our styles are different does not make either one of them right or wrong. It's a bit strong to say that your code is right, because that implies that my code is wrong. Let's agree that it is *your opinion* that "two steps" is the right/better way or *you prefer* "two steps." As a highly respected individual in this community, you affect the perceived usefulness of every node on which you comment. And I, for one, don't want to believe that you are an arrogant person, so please update your node to reflect that it *is* your opinion rather than a matter of fact.

        I'm not saying that my approach is the sole possible correct one, but I do believe that yours is wrong. The way it turns out to work is more of an accidental feature than something you should rely on. If you really don't want to create a temporary array, I suggest you do something like
        map func1([ splice @_, 0, $num_elements ]), 0 .. (@_/$num_elements);
        Iterating the map over an array while modifying it at the same time is wrong.

        Makeshifts last the longest.

Re: Fold a list using map splices
by BrowserUk (Pope) on Feb 21, 2003 at 17:00 UTC

    I've used the splice solution, but it offended me that you have to copy the array just to destroy it. I came up with a version of my mapn function that avoids the splice (and therefore the need to copy as well), by using slices and a pointer.

    There are two versions of the function mapn and mapnz. With the former, number of values produced in the last row is just how ever many are left over.

    With the latter, it pads (with nulls, hence the 'z') the last list of returned values to be the same size as the rest of the rows. This can be useful for things like tables, where although most browsers will tolorate there being less <td></td> pairs in the last row, they do not render this very well. By using mapnz in conjunction with an 8-char patch I made to CGI.pm so that empty TD's have a &nbsp; inserted, forces most browsers to render the borders around missing values.

    #! perl -slw use strict; use CGI qw[td Tr :table caption]; sub mapn (&@) { my($c, $n, $s) = (shift, shift, 0); map{ $s+=$n; $c->( @_[($s-$n) .. ($s < @_ ? $s-1 : @_-1) ] ); } 0 .. (@_/$n); } sub mapnz (&@) { my($c, $n, $s) = (shift, shift, 0); map{ $s+=$n; $c->( @_[($s-$n) .. ($s - 1) ] ); } 0 .. (@_/$n); } my @tabular_data = map{ 1000+int rand 8999 } 1 .. 31; print table( {border=>1}, $/, mapn{ Tr( td( \@_ ) ).$/; } 5, @tabular_data ); print table( {border=>1}, $/, caption('Columns: ' . $_), $/, mapnz{ Tr( td( \@_ ) ).$/ } $_, @tabular_data ) for 3 .. 8; __END__ C:\test>236799 <table border="1"> <tr><td>4978</td> <td>7782</td> <td>3230</td> <td>3524</td> <td>7570< +/td></tr> <tr><td>9627</td> <td>4424</td> <td>7968</td> <td>2523</td> <td>1838< +/td></tr> <tr><td>9843</td> <td>8797</td> <td>5034</td> <td>8499</td> <td>3683< +/td></tr> <tr><td>5591</td> <td>2402</td> <td>6131</td> <td>2649</td> <td>5917< +/td></tr> <tr><td>1333</td> <td>7874</td> <td>7031</td> <td>2269</td> <td>3766< +/td></tr> <tr><td>2151</td> <td>1350</td> <td>7235</td> <td>5986</td> <td>1447< +/td></tr> <tr><td>2627</td></tr> </table> <table border="1"> <caption>Columns: 3</caption> <tr><td>4978</td> <td>7782</td> <td>3230</td></tr> <tr><td>3524</td> <td>7570</td> <td>9627</td></tr> <tr><td>4424</td> <td>7968</td> <td>2523</td></tr> <tr><td>1838</td> <td>9843</td> <td>8797</td></tr> <tr><td>5034</td> <td>8499</td> <td>3683</td></tr> <tr><td>5591</td> <td>2402</td> <td>6131</td></tr> <tr><td>2649</td> <td>5917</td> <td>1333</td></tr> <tr><td>7874</td> <td>7031</td> <td>2269</td></tr> <tr><td>3766</td> <td>2151</td> <td>1350</td></tr> <tr><td>7235</td> <td>5986</td> <td>1447</td></tr> <tr><td>2627</td> <td>&nbsp;</td> <td>&nbsp;</td></tr> </table>

    Examine what is said, not who speaks.
    1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
    2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
    3) Any sufficiently advanced technology is indistinguishable from magic.
    Arthur C. Clerk.
      The index variables there kept tickling me as not right. This is Perl, not C, there has to be a way to do this without explicit indices and no copying. Guess what. :) It's actually simpler than I was expecting and definitely goes in my "cool snippets" file.
      sub mapn (&@) { my ($cb, $n) = splice @_, 0, 2; splice @_, 0, 0, (undef)x$n; map{ splice @_, 0, $n; $cb->(@_[0 .. (@_ < $n ? $#_ : $n-1)]); } 0 .. (@_/$n)-1; } sub mapnz (&@) { my ($cb, $n) = splice @_, 0, 2; splice @_, 0, 0, (undef)x$n; map{ splice @_, 0, $n; $cb->(@_[0 .. $n-1]); } 0 .. (@_/$n)-1; }

      BrowserUk++ for the inspiration!

      Update: fixed mapnz

      Makeshifts last the longest.

        Nice twist++. Took me a while to work out why i doesn't destroy the input array. Not sure that I share your distaste for the index variable...'sides...mine's quicker:).

        You have a typo? ... an out-by-one error on your version of mapnz, it produces one too many rows. You either need to subtract 1 as you do in the mapn version or start your range from 1.


        Examine what is said, not who speaks.
        1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
        2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
        3) Any sufficiently advanced technology is indistinguishable from magic.
        Arthur C. Clarke.
Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (3)
As of 2021-03-07 03:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My favorite kind of desktop background is:











    Results (119 votes). Check out past polls.

    Notices?