10 декабря 2011

10 Perl One Liners to Impress. 10 Perl однострочников

Вытащено из кэша Yahoo, у Google уже не было. Специально для Habrahabr.ru

10 Perl One Liners to Impress(?) Your Friends

published 05 Jun 2011

On May 31st, Marcus Kazmierczak posted 10 one-liners in Scala, each designed to show off a strength of the language. It was quickly Rosetta Stoned into many other languages including Python and Ruby.

The title is a bit of a misnomer, as the one-liners aren’t particularly impressive (and sometimes aren’t even “one-liners” in the Perl sense). I thought I would convert them to Perl mainly to compare with other languages.

Update: I’ve incorporated some improvements from Evan Carroll (see his Disqus post below). Thanks Evan!

1. Multiply each item in a list by 2

say $_ * 2 for 1..10;

If assigning to an array, you can use map: map { $_ * 2 } 1..10 compares favourably with Scala’s (1 to 10) map { _ * 2 }.

2. Sum a list of numbers


We’ll use List::Util which comes with Perl.

use List::Util qw(sum);
say sum 1..1000;

Alternately, a true one-liner:

perl -MList::Util=sum -E 'say sum(1..1000)'

Using reduce from the same module:

say reduce { $a + $b } 1..1000;

3. Check if word exists in a string


This is a Perlish solution:

my @wordlist = ("scala", "akka", "play framework", "sbt", "typesafe");
my $tweet = "This is an example tweet talking about scala and sbt.";
say for grep { $tweet =~ /\b$_\b/ } @wordlist;

The \b thingies here represent ‘word boundaries’, which include whitespace but also punctuation. This allows us to match ‘scala’ (surrounded by spaces) but also ‘sbt’ (wedged between a space and a period).

You can also use split:

my @tweet_words = split /\W/, $tweet;
say for grep { $_ ~~ @tweet_words } @wordlist;

The \W splits on anything that isn’t a ‘word’ (UTF-8 fans are going to harang me for this; it’s blatantly wrong). The ~~ smart-match operator is like Python’s in: it looks for the word in the array.

If you have a large number of words to search against, it’s better to use a hash:

my %tweet_words;
$tweet_words{$_} = 1 for split /\W/, $tweet;
say for grep { $tweet_words{$_} } @wordlist;

4. Read in a file

use File::Slurp qw(slurp);
my $file = slurp("filename"); # read_file also works

Before File::Slurp people came up with all kinds of abominations to do this:

my $file = do { local(@ARGV,$/)="filename";<>};

The above code unsets $/ (the input record separator), so Perl reads everything in at once. It sets @ARGV to “filename” so that the <> operator reads that file in (saving a call to open). On the command line, the -0777 option does the same job (see perlrun):

perl -0777E 'my $file = <>; print $file' filename

5. Happy birthday to you

say "Happy Birthday to " . ($_ == 2 ? "dear Name" : "you") for 0..3;

6. Filter list of numbers


part partitions the input into two boxes based on the result of $_ > 60: box 0 (false) or box 1 (true).

use List::MoreUtils qw(part);
my ($failed, $passed) = part { $_ > 60 } (49, 58, 76, 82, 88, 90);

It’s more common to grep for one case at a time.

my @passed = grep { $_ > 60 } (...);

7. Fetch and parse an XML service


Perl tries not to care that you use XML, but it hates you a little inside.

use LWP::Simple qw(get);
use XML::Simple;

my $data = XMLin(get("http://search.twitter.com/search.atom?&q=xml+sucks"));

8. Minimum and maximum of a list


minmax does both in one go:

use List::MoreUtils qw(minmax);
my ($min, $max) = minmax @LIST;

9. Parallel processing


I really like Scala’s implementation of “parallel collections”, which is worth mentioning:

val result = dataList.par.map(line => processItem(line))

This is the best I could find on CPAN:

use Parallel::Iterator qw(iterate_as_array);

my @result = iterate_as_array( sub { processItem($_[1]) }, \@dataList );

Parallel::Iterator sounds like an awesome module, so give it a try if you need to do some multi-core computation!

10. Sieve of Eratosthenes


Okay, I’m getting bored of all this readable Perl code. Let’s bust out the big guns, courtesy of tye and tilly:

sub sieve3 {
grep{@_[map$a*$_,2..@_/($a=$_)]=0if$_[$_]>1}@_=0..pop
}
print join " ", sieve(100);

And since we’re talking about Perl and primes, I can’t help but mention Abigail’s regular expression for testing prime numbers (!):

perl -wle 'print "Prime" if (1 x shift) !~ /^1?$|^(11+?)\1+$/' [number]

(How it works.)

Conclusion


Blindly translating examples from one script to another feels like a kind of pan-language pissing contest, but can be educational and shows how Perl deals with domains it wasn’t originally designed for. In that respect, Perl does quite well considering how venerable the language is. Perl may not have as many built-ins as other languages, particularly in the functional domain, but CPAN can fill in for virtually anything that’s missing.



Logout

Add New Comment

jkeks


  • Post as jkeks


  • Image



  • Share on:
    Twitter

          Sort by popular now                   Sort by best rating                   Sort by newest first                   Sort by oldest first        

Showing 4 comments




  • Evan Carroll

    Most of these aren't one liners, they're just expressions. Most of them can also be written better...
    Here you're iterating twice for no reason..
    perl -E 'say for map { $_ * 2 } 1..10'
    Just do, -E'say $_*2 for 1..10'
    Your algo for checking if a word exists in a string is slow. If one liners don't matter...
    my %tweet_words;
    $tweet_words{$_}=1 for split /\W/, $tweet;
    say $tweet_words ? "found [$_]" : "not found [$_"] for @wordlist;
    List::Util is nice, but for one liners this will achive the same effect.
    perl -00E'my $foo = <>; print $foo' foobar.txt
    Again, you seem to be hellset on using `for` and `map`... you should pick.
    say for map { "Happy Birthday to " . ($_ == 2 ? "dear Name" : "you") } 0..3;
    say "Happy Birthday to " . ($_ == 2 ? "dear Name" : "you") for 0..3;






  • rjh29

    I used map because it's a direct analog to the Scala solution in the original article - then tacked a 'say for' at the beginning and didn't think about it. D'oh! Same deal with using smart-match rather than a hash lookup (because other languages have an 'in' operator). Thanks, though! Do you mind if I add your improvements to the article?
    Btw, checking 'exists $tweet_words{$_}' seems to be about 5-10% faster than not using exists on my machine (Perl 5.10.1 on Ubuntu 10.04).






  • Evan Carroll

    the fastest thing is to set the hash value to undef which doesn't have ref counting, and is an internal pointer to SV_UNDEF, and to use exists(). However, that's reasonably more ugly. And, the point is you can at least stay O(n) with a hash, non-set array intersections are always O(n**2) which gets bad fast.
    You can use all of the suggestions, also this rule is universal. One loop is always better.
    Not:
    say for grep { $tweet =~ /\b$_\b/ } @wordlist;
    But:
    for (@wordlist) { say if /\b$_\b/ }






  • Craig DeForestCollapse

    It's interesting that so many of your examples are numeric but you haven't tried "use PDL;", which loads a scientific grade numerical processing package. PDL minmax is between 30 and 150 times faster than List::MoreUtils::minmax (it gets the gain by working on traditional structured arrays of numbers rather than on Perl lists of polymorphous scalars).





Trackback URL

Richard Harris 2011 ©


Видеочат рулетка