http://qs321.pair.com?node_id=1221104

lunix has asked for the wisdom of the Perl Monks concerning the following question:

Hi all

The task is the following: We have an array of words for example ["anyone", "cancel", "declare", "perlmonks"]. We need to print the word, if it contains an "a" and also the number of "e"-s in that word. The syntax has to be: "word: number". Easy, right? Here is a perl program that does this and is very easy to understand:
#!/usr/bin/perl -w use strict; my @words = ("anyone", "cancel", "declare", "perlmonks"); my $count = 0; foreach (@words) { if ($words[$count] =~ m/a/i) { my $number = () = $words[$count] =~ /e/gi; print "$words[$count]: $number\n"; } $count ++; }

This outputs:
anyone: 1 cancel: 1 declare: 2

Just for fun, I am searching for the shortest perl one-liner that does the same. This is the shortest version I could come up with, with my current knowledge of perl:
perl -e 'for('anyone','cancel','declare','perlmonks'){$i=()=/e/g;print +"$_: $i\n"if(/a/);}'

I was wondering what kind of magic solution the great minds here could come up with. Feel free to impress me! :)

Update: Current shortest version is

perl -E'/a/&&say"$_: ".y/e//for anyone,cancel,declare,perlmonks'

Replies are listed 'Best First'.
Re: Shorten this one-liner!
by LanX (Saint) on Aug 25, 2018 at 21:56 UTC
    Update: Code shown is based on the last one-liner of the OP which was case sensitive.

    For case insensitivity replace /a/ with /a/i and y/e/.. with y/eE/.. in the following examples.


    For a start, I tried this

    c:\Perl_524>perl -anE"/a/&&say$_,' :'.y/$_/e/for@F" anyone cnacel declare perlmonks anyone :0 cnacel :0 declare :0
    but am failing to call tr (aka y) in scalar context.

    update
    I misread the docs for tr, is this good enough?
    c:\Perl_524>perl -anE"/a/&&say$_.' :'.y/e/e/for@F" anyone cancel declare perlmonks anyone :1 cancel :1 declare :2

    note: you have to enter more lines or kill the one liner with C-c

    update

    one char less!

    c:\Perl_524>perl -anE"/a/&&say$_.' :'.y/e//for@F" anyone cancel declare perlmonks anyone :1 cancel :1 declare :2

    explanation: this is equivalent

    use feature 'say'; while (<>) { @F = split(' '); /a/ and say $_ . ' :' . tr/e// foreach (@F); }

    see also perlrun for -a, -n and -E

    (though it claims "-a implicitly sets -n" which I can't reproduce)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

    update

    even less under linux

    ~$ perl -anE'/a/&&say"$_ :".y/e//for@F' anyone cancel declare perlmonks anyone :1 cancel :1 declare :2

    update
    last but not least without reading from STDIN (which doesn't make much sense for a one-liner)

    $ perl -E'/a/&&say"$_ :".y/e//for qw/anyone cancel declare perlmonks/' anyone :1 cancel :1 declare :2
      Thank you for taking the time and replying! I tried using say, but I have never done it before, so I didn't succeed that way. And I knew the substitution (s) operator returned by default the amount of substitutions made, but I couldn't use it on a read-only value, so tr was the asnwer, but I didn't know it existed! Always good to learn something new.
      Your final code was

      perl -E'/a/&&say"$_ :".y/e//for qw/anyone cancel declare perlmonks/'

      Which is great, and already shorter than mine, but I was able to get it even shorter!

      perl -E'/a/&&say"$_ :".y/e//for anyone,cancel,declare,perlmonks'

      And reading from STDIN is a good idea, (and shortens the code even furter), but I wanted to get a one-liner that can just be copy-pasted in itself and does the job, without having to type anything.

      Cheers, lunix
Re: Shorten this one-liner!
by eyepopslikeamosquito (Archbishop) on Aug 26, 2018 at 07:00 UTC

    To clarify that a word containing an 'a' but no 'e' should be printed, I've added an extra "a" word to your test data. Your program:

    for('a','anyone','cancel','declare','perlmonks'){$i=()=/e/g;print"$_: +$i\n"if(/a/);}
    and mine:
    /a/&&print"$_: ".y/e//.$/for'a','anyone','cancel','declare','perlmonks +'
    both print the identical output namely:
    a: 0 anyone: 1 cancel: 1 declare: 2
    but mine is 13 characters shorter.

    For golf problems of this type I prefer to put the data not in code, but in a separate file read by the program from stdin. For example, for a data file t1.txt containing:

    a anyone cancel declare perlmonks
    we can run:
    perl -lne"/a/&&print$_.': '.y/e//" t1.txt
    to produce the same output.

    Update: Just saw LanX -E switch update. For modern versions of perl, we can shorten to:

    perl -lnE"/a/&&say$_.': '.y/e//" t1.txt
    which should work everywhere. For Unix only, we can save another stroke or two by using -lnE'...' instead of -lnE"...".

      Hi, thanks for replying! Reading input from a file is a good idea, but I wanted to get a one-liner that can just be copy-pasted in itself and does the job, without having to type anything. See current shortest version in original post.
Re: Shorten this one-liner!
by eyepopslikeamosquito (Archbishop) on Aug 26, 2018 at 12:22 UTC

    Update: Current shortest version is
    perl -E'/a/&&say"$_: ".y/e//for anyone,cancel,declare,perlmonks'
    It's your competition, so you're free to set the rules ... though be aware that your solution, while short, is not super robust or portable. Re robustness, try changing "declare" to "delete" :) -- that is, while it works for your four chosen words, there are many barewords that will require quoting. Re portability, note that -E'...' works fine on Unix but fails on Windows (which needs -E"..." instead).

    BTW, since you're interested in code golf, you might be interested in some of the articles I've written over the years on this topic, for example:

    and many more listed in the Golf section of my home node eyepopslikeamosquito.

      Wow! Very interesting reading, you have provided, I like it much.
      This task (printing the word, if it contains "a" and then the occurrences of "e") came up years ago in python programming class and some could't do it. I was just trying to show them how uncomplicated it was by taking my perl code and turning it into a one-liner. Guess I was trying to be cool. And that was it, it was saved on my hard drive and forgotten. Now, years later I found the one-liner, which looked like (obviously with different words):

      perl -e '$c=0;@s=("anyone","cancel","declare","perlmonks");foreach(@s){if($s[$c]=~/a/){$n=()=$s[$c]=~/e/g;print"$s[$c]: $n\n";}$c++;}'
      (135 strokes)

      And I had to laugh. This didn't contain any really "perl-y" tricks (except for the regexp maybe), it was my original program without any whitespaces, and shorter variables. So in a few hours I got it down to

      perl -e 'for('anyone','cancel','declare','perlmonks'){$i=()=/e/g;print"$_: $i\n"if(/a/);}'
      (91 strokes)

      And here I got stuck. But I enjoyed getting it shorter, and I knew it could be even shorter. So I posted it here, to see how far I was from the shortest solution. Now we have

      perl -E'/a/&&say"$_: ".y/e//for anyone,cancel,declare,perlmonks'
      (65 strokes)

      And it might get shorter. I don't know.
      Also, this isn't really a competition, more like, just me trying to improve my skills. I was curious how differently others might approach it.

      PS. I never heard about code golf before, but thanks for introducing me to it, I might get into it with time :)

      Regards, lunix
        To win at normal golf, you have to have the minimum number of strokes for the course.
        Perl "Golf" is similar, the shortest code that does the job (fewest key stokes) wins!

        This Perl golf stuff is meaningless aside from an interesting puzzle.
        I recommend getting solid with your Perl coding skills before writing one-liners.

Re: Shorten this one-liner!
by Marshall (Canon) on Aug 25, 2018 at 23:28 UTC
    I did not find your code particularly easy to understand.
    An alternate coding could be like the following...
    tr is a lot faster than using the regex engine.
    #!/usr/bin/perl use warnings; use strict; my @words = ("anyone", "cancel", "declArE", "perlmonks", "pal"); # Note the "pal" case is an extra case that I added foreach my $word (@words) { if ($word =~ tr/aA//) # word contains an "A" or "a" { my $numE = $word =~ tr/eE//; print "$word $numE\n"; #number of e's in word with an "a" } } __END__ anyone 1 cancel 1 declArE 2 pal 0
    I think shortening the code to a one-liner is a meaningless exercise.

    Update: I don't see the reason to make a "one liner" other than to do it for the fun of doing it. This makes no difference in execution speed. White space and comments consume no MIPS and the difference in Perl compile speed is insignificant. This $words[$count] is very anti-Perl philosophy... subscripts are rare. Learn more Perl before trying to write a one-liner.

    Another Update:
    This will be about the same performance as the above code:

    my @words = ("anyone", "cancel", "declArE", "perlmonks", "pal"); print "$_: ",tr/eE//, "\n" for grep{tr/aA//;}@words;
    I asked a Python friend and here is one result:
    words = ["anyone", "cancel", "declArE", "perlmonks", "pal"] print(*((word,word.upper().count('E')) for word in words if 'A' in wor +d.upper()))
    I like my Perl code much better!