Algorithm-Loops

 view release on metacpan or  search on metacpan

lib/Algorithm/Loops.pm  view on Meta::CPAN

    use Algorithm::Loops qw( Filter );

it gets declared before the rest of our code is compiled so we don't have
to use parentheses when calling it.  We I<can> if we want to, however:

        my @copy= Filter( sub {s/\s/_/g}, @list );

=head3 Note on "Function BLOCK LIST" bugs

Note that in at least some versions of Perl, support for the "Filter
BLOCK ..." syntax is somewhat fragile.  For example:

    ... Filter( {y/aeiou/UAEIO/} @list );

may give you this error:

    Array found where operator expected

which can be fixed by dropping the parentheses:

    ... Filter {y/aeiou/UAEIO/} @list;

So if you need or want to use parentheses when calling Filter, it is best
to also include the C<sub> keyword and the comma:

    #         v <--------- These ---------> v
    ... Filter( sub {y/aeiou/UAEIO/}, @list );
    # require   ^^^ <--- these ---> ^ (sometimes)

so your code will be portable to more versions of Perl.

=head3 Examples

Good code ignores "invisible" characters.  So
instead of just chomp()ing, consider removing
all trailing whitespace:

    my @lines= Filter { s/\s+$// } <IN>;

or

    my $line= Filter { s/\s+$// } scalar <IN>;

[ Note that Filter can be used in a scalar
context but always puts its arguments in a
list context.  So we need to use C<scalar> or
something similar if we want to read only one
line at a time from C<IN> above. ]

Want to sort strings that contain mixtures of
letters and natural numbers (non-negative
integers) both alphabetically and numerically
at the same time?  This simple way to do a
"natural" sort is also one of the fastest.
Great for sorting version numbers, file names,
etc.:

    my @sorted= Filter {
        s#\d{2}(\d+)#\1#g
    } sort Filter {
        s#(\d+)# sprintf "%02d%s", length($1), $1 #g
    } @data;

[ Note that at least some versions of Perl have a bug that breaks C<sort>
if you write C<sub {> as part of building the list of items to be sorted
but you don't provide a comparison routine.  This bug means we can't
write the previous code as:

    my @sorted= Filter {
        s#\d{2}(\d+)#\1#g
    } sort Filter sub {
        s#(\d+)# sprintf "%02d%s", length($1), $1 #g
    }, @data;

because it will produce the following error:

    Undefined subroutine in sort

in some versions of Perl.  Some versions of Perl may even require you
to write it like this:

    my @sorted= Filter {
        s#\d{2}(\d+)#\1#g
    } sort &Filter( sub {
        s#(\d+)# sprintf "%02d%s", length($1), $1 #g
    }, @data );

Which is how I wrote it in ex/NaturalSort.plx. ]

Need to sort names?  Then you'll probably want to ignore letter case and
certain punctuation marks while still preserving both:

    my @compare= Filter {tr/A-Z'.,"()/a-z/d} @names;
    my @indices= sort {$compare[$a] cmp $compare[$b]} 0..$#names;
    @names= @names[@indices];

You can also roll your own simple HTML templating:

    print Filter {
        s/%(\w*)%/expand($1)/g
    }   $cgi->...,
        ...
        $cgi->...;

Note that it also also works correctly if you change how you output your
    HTML and accidentally switch from list to scalar context:

    my $html= '';
    ...
    $html .= Filter {
        s/%(\w*)%/expand($1)/g
    }   $cgi->...,
        ...
        $cgi->...;

=head3 Motivation

A reasonable use of map is:

    @copy= map {lc} @list;

which sets @copy to be a copy of @list but with all of the elements
converted to lower case.  But it is too easy to think that that could
also be done like this:

    @copy= map {tr/A-Z/a-z/} @list;  # Wrong

The reason why these aren't the same is similar to why we write:

    $str= lc $str;

not

    lc $str;  # Useless use of 'lc' in void context

and we write:

    $str =~ tr/A-Z/a-z/;

not

    $new= ( $old =~ tr/A-Z/a-z/ );  # Wrong

That is, many things (such as lc) return a modified copy of what they are
given, but a few things (such as tr///, s///, chop, and chomp) modify

lib/Algorithm/Loops.pm  view on Meta::CPAN


=item MapCar(\&@)

=item MapCarU(\&@)

=item MapCarE(\&@)

=item MapCarMin(\&@)

=back

=head3 Usage

The MapCar* functions are all like C<map> except they each loop over more
than one list at the same time.

[ The name "mapcar" comes from LISP. As I understand it, 'car' comes from
the acronym for a register of the processor where LISP was first
developed, one of two registers used to implement lists in LISP.  I only
mention this so you won't waste too much time trying to figure out what
"mapcar" is supposed to mean. ]

The MapCar* functions all have prototype specifications of (\&@).

This means that they demand that the first argument that you pass be a
CODE reference.  After that you should pass zero or more array references.

Your subroutine is called (in a list context) and is passed the first
element of each of the arrays whose references you passed in (in the
corresponding order).  Any value(s) returned by your subroutine are
pushed onto an array that will eventually be returned by MapCar*.

Next your subroutine is called and is passed the B<second> element of
each of the arrays and any value(s) returned are pushed onto the results
array.  Then the process is repeated with the B<third> elements.

This continues until your subroutine has been passed all elements [except
for some cases with MapCarMin()].  If the longest array whose reference
you passed to MapCar() or MapCarU() contained $N elements, then your
subroutine would get called $N times.

Finally, the MapCar* function returns the accumulated list of values.  If
called in a scalar context, the MapCar* function returns a reference to
an array containing these values.

[ I feel that having C<map> return a count when called in a scalar
context is quite simply a mistake that was made when this feature was
copied from C<grep> without properly considering the consequences.
Although it does make for the impressive and very impractical golf
solution of:

    $sum=map{(1)x$_}@ints;

for adding up a list of natural numbers. q-: ]

=head3 Differences

The different MapCar* functions are only different in how they deal with
being pqssed arrays that are not all of the same size.

If not all of your arrays are the same length, then MapCarU() will pass
in C<undef> for any values corresponding to arrays that didn't have
enough values.  The "U" in "MapCarU" stands for "undef".

In contrast, MapCar() will simply leave out values for short arrays (just
like I left the "U" out of its name).

MapCarE() will croak without ever calling your subroutine unless all of
the arrays are the same length.  It considers it an Error if your arrays
are not of Equal length and so throws an Exception.

Finally, MapCarMin() only calls your subroutine as many times as there
are elements in the B<shortest> array.

In other words,

    MapCarU \&MySub, [1,undef,3], [4,5], [6,7,8]

returns

    ( MySub( 1, 4, 6 ),
      MySub( undef, 5, 7 ),
      MySub( 3, undef, 8 ),
    )

While

    MapCar \&MySub, [1,undef,3], [4,5], [6,7,8]

returns

    ( MySub( 1, 4, 6 ),
      MySub( undef, 5, 7 ),
      MySub( 3, 8 ),
    )

While

    MapCarMin \&MySub, [1,undef,3], [4,5], [6,7,8]

returns

    ( MySub( 1, 4, 6 ),
      MySub( undef, 5, 7 ),
    )

And

    MapCarE \&MySub, [1,undef,3], [4,5], [6,7,8]

dies with

    MapCarE: Arrays with different sizes (3 and 2)

=head3 Examples

Transposing a two-dimensional matrix:

    my @transposed= MapCarE {[@_]} @matrix;

or, using references to the matrices and allowing for different row
lengths:

    my $transposed= MapCarU {[@_]} @$matrix;

Formatting a date-time:

    my $dateTime= join '', MapCarE {
        sprintf "%02d%s", pop()+pop(), pop()
    } [ (localtime)[5,4,3,2,1,0] ],
      [ 1900, 1, (0)x4 ],
      [ '// ::' =~ /./g, '' ];

Same thing but not worrying about warnings for using undefined values:

    my $dateTime= join '', MapCarU {
        sprintf "%02d%s", pop()+pop(), pop()
    } [ (localtime)[5,4,3,2,1,0] ],
      [ 1900, 1 ],
      [ '// ::' =~ /./g ];

Combine with C<map> to do matrix multiplication:

    my @X= (
        [  1,  3 ],
        [  4, -1 ],
        [ -2,  2 ],
    );
    my @Y= (
        [ -6,  2, 5, -3 ],
        [  4, -1, 3,  1 ],
    );
    my @prod= map {
        my $row= $_;
        [
            map {
                my $sum= 0;
                $sum += $_   for  MapCarE {
                    pop() * pop();
                } $row, $_;
                $sum;
            } MapCarE {\@_} @Y;
        ]
    } @X;

Report the top winners:

    MapCarMin {
        print pop(), " place goes to ", pop(), ".\n";
    } [qw( First Second Third Fourth )],
      \@winners;

Same thing (scalar context):

    my $report= MapCarMin {
        pop(), " place goes to ", pop(), ".\n";
    } [qw( First Second Third Fourth )],
      \@winners;

Displaying a duration:

    my $ran= time() - $^T;



( run in 2.773 seconds using v1.01-cache-2.11-cpan-5b529ec07f3 )