הלינקייה: מגזין חודשי למפתחים

רוצה לשמוע על כל האירועים, המדריכים, הקורסים והמאמרים שנכתבו החודש ?
הלינקייה הינו מגזין חופשי בעברית שמשאיר אותך בעניינים.
בלי ספאם. בלי שטויות. פעם בחודש אצלך בתיבה.

Lists Manipulations

One of the many influences of perl is functional programming. The functional programming paradigms takes functions as first class entities, and emphasizes the power of immutability (that is, not changing stuff). Although working with functions as first class citizens will require us to work with references - something we will only learn later on - we can still make some very good use of these immutable ideas with the tools we have learned so far.
This lesson we explore the following functions and their use in working on data structures without changing them. Here's the summary of the functions we will encounter:

Function Description
map Runs a function for every element in a list, returning a list of the results
grep Tests each element in a list against a condition, returning a list of the matches
reduce Reduces a list by running accumulative code on each element

On the first sight, it's not clear why these functions are better than simply using loops. The truth is every functional construct is easily implemented using loops. Yet, when we use the functional construct, we provide a different thinking approach to the problem. When appropriate, thinking in functions simply makes clearer code.

Let's examine some typical problems.

Count Prime Numbers

We start with a simple case of filtering a list. The is_prime subroutine is straight forward, just loop to try and find dividers, if there are none we've got a prime. In the general case, we have some function that can tell if a value "fits" or "doesn't fit". To get a list of only the primes, we grep all the numbers with the is_prime subroutine. The grep operation returns a new list of only the primes. Some points to remember about grep:

  • grep does not change the original list. That's the reason we can run it on a constant list and keep everyone happy
  • grep takes a code block and a list. There's no need for a comma between them, perl will fill the gap.
  • grep is very fast. It's usually faster to grep a list than to splice it.
use strict;
use warnings;
use Carp;

my $start = shift || 2;
my $end   = shift || 100;

sub is_prime {
  my $num = shift or croak "Missing Argument";
  foreach my $divider ( 2..$num/2 ) { 
      return 0 if $num % $divider == 0;
  }
  return 1;
}

my $count = grep { is_prime $_ } ($start..$end);
print "There are $count primes between $start and $end\n";

Quote Input Text

The following reads lines of input until EOF, wraps each line in quotes and puts everything into the @quoted list. We use map function to map between the original list of input lines to the new list of quoted input lines. In general, map is a good way to solve problems involving converting from one data format to another.

use strict;
use warnings;

my @quoted = map { chomp; "\"$_\"" } <>; 
foreach my $line (@quoted) {
    print "John said: $line\n";
}

Find Longest Line

Calculating the sum of all elements

I will demonstrate two ways to find the sum of a list, both use functional routines from the List::Util module. List::Util is a core perl module that is installed with every distribution of perl. It provides many subroutines for functional list processing, including the do-it-all reduce and some other utility subroutines.
Do take the time to read its man page. It will surely save you some typing in the future.

use strict;
use warnings;
use List::Util qw(reduce);

# suppress the warning
local ($a, $b);

my $sum  = reduce { $a + $b } (1..100);
print "sum of 1..100 is: $sum\n";

use strict;
use warnings;
use List::Util qw(sum);

print sum (1..100), "\n";

Inverse a dictionary

Dictionaries are very useful when we have a key and looking for the corresponding value, but they're not that good at reverse searching. The following technique inverses a dictionary, thus letting us perform reverse searches (fetch a key by its value).
The program uses a nice feature of map - returning two values is interpreted as a dictionary.

use strict;
use warnings;
use Data::Dumper;

my %dict = ( 
    a => 10, 
    b => 20, 
    c => 30, 
    d => 40, 
);

my %inversed = map { $dict{$_}, $_ } keys %dict;
print Dumper(\%inversed);

For more information about functional programming in perl, please refer to:
Higher-Order Perl: Transforming Programs with Programs

course: