Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

printing every 2nd entry in a list backwards

by Anonymous Monk
on May 19, 2017 at 11:52 UTC ( #1190600=perlquestion: print w/replies, xml ) Need Help??
Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hallowed monks,

lowly Perl apprentice here with a question regarding list traversal.

I chanced upon this code written in a lesser language:

import sys f = open(sys.argv[1], "r") for line in f: for n in line.split()[::-2]: sys.stdout.write(n + " ") print("");

Being ever curious I wondered how this could be done in Perl.

Trying to wield what little power of Perl I possess, I came up with the following:

use strict; use warnings "all"; open(my $fh, "<", "$ARGV[0]") or die; while (<$fh>) { chomp; my $i = 0; map {print $i++ & 1 ? "" : "$_ "} reverse split(/ /); print "\n"; } close $fh;

The task seems simple at a glance:

Given a line of numbers from a file, print every 2nd number starting from the back.

Example:

1 22 3 -4 ==> -4 22

Are the esteemed monks able to enlighten me with a solution that is better and more performant?

Faithfully yours,

Perl apprentice

Replies are listed 'Best First'.
Re: printing every 2nd entry in a list backwards (updated)
by haukex (Monsignor) on May 19, 2017 at 12:13 UTC
    $ cat in.txt 1 22 3 -4 a b c d e f g h i j k l m n o $ perl -anle 'print "@F[map $#F-$_*2, 0..$#F/2]"' in.txt -4 22 a c f d j h o m k

    But seriously, there are lots of ways to do this. Just one of many:

    while (<>) { my @fields = split; for (my $i=$#fields; $i>=0; $i-=2) { print "$fields[$i] "; } print "\n"; }

    Update: As for your code, I'd probably have used for instead of map, but other than that it's a decent solution. Just for fun, a different way to write that might be: ++$i&1 and print "$_ " for reverse split; although that might be getting a little too clever ;-) This is a fun exercise in TIMTOWTDI! Update 2: Changed wording a bit.

      Thank you kindly for your reply.

      Your first solution is indeed exuding the knowledge of a monk, sadly it is rather slow.

      Your second solution is actually what I tried first but it is also slower than my current solution. I am explicitly looking for a fast solution to beat the solution written in the language that also starts with "P" and must not be named.

      I apologize for not being clear in the first place.

        Your second solution is actually what I tried first but it is also slower than my current solution.

        How exactly did you measure this - code, sample input, etc.? Because at least under the following conditions, my code suggestion appears to be faster than yours. (Note I inserted use warnings; use strict; at the top of my script.)

        $ perl -wMstrict -le 'print join(" ",map {int(rand(100))-50} 0..rand(7 +)+3) for 1..1000000' >in.txt $ wc -l in.txt 1000000 in.txt $ head -5 in.txt -49 43 0 -35 0 -20 -49 5 46 -11 -14 39 39 -24 -49 36 -7 -36 -43 15 30 5 -4 11 37 -25 27 -49 21 49 33 -15 -16 17 10 32 -14 -30 $ time perl 1190600.pl in.txt >out.txt real 0m1.869s user 0m1.863s sys 0m0.004s $ time perl 1190602.pl in.txt >out.txt real 0m1.539s user 0m1.521s sys 0m0.016s
Re: printing every 2nd entry in a list backwards
by KurtZ (Monk) on May 19, 2017 at 12:38 UTC
    Print is slow, you can try to speed up your solution by taking it out of the loop
    print map {$i++ & 1 ? "" : "$_ "} reverse split(/ /);
    Also a common trick for a flip flop boolean state is inversion with xor $i^=1
    In hindsight you want grep not a map
    $,=" "; print grep { $i^=1 } reverse split(/ /);

    updated $,=" " , haukex++
      Don't forget about
      $, = " "
      to make sure the output is properly formatted.

      The one with grep is amazingly fast, kudos

Re: printing every 2nd entry in a list backwards
by 1nickt (Prior) on May 19, 2017 at 12:43 UTC

    Another way:

    my @x = (1, 22, 3, -4 ); print join " ", grep { state $i; $i++ % 2 == 0 } reverse @x;

    The documentation advises against using map purely for its side effects. If you're not going to use the resulting list, use a for loop instead, or in the case of simply filtering a list down to a smaller list, grep.

    As far as performant, I don't think you can tell much about the performance of the loop when you are opening and closing a file and only processing one line with four elements: the I/O will eat up most of the time no matter the solution.

    Hope this helps!

    update: show with grep


    The way forward always starts with a minimal test.

      You are indeed correct with your observations about performance. However this can be easily mended by creating a file with 1_000_000 entries, each line having 10 numbers and then running the script against it

      Doing this on my machine, the despicable snake language comes out ahead

      $ time perl list.pl in >/dev/null real 0m2.150s user 0m2.136s sys 0m0.004s $ time python list.py in >/dev/null real 0m1.426s user 0m1.420s sys 0m0.000s

      I am looking to the monks for help on this one

        Doing this on my machine, the despicable snake language comes out ahead

        Even a snake can do what other beasts can't. No need to worry. Perl doesn't have a stepping range operator, it doesn't even have a descending one. So the indices must be built every line through. OTOH, knowing that the input lines always contain 10 items, it is trivial to beat the snake:

        qwurx [shmem] ~> time perl -lne 'BEGIN{$,=" "}print+(split)[9,7,5,3,1] +' in.txt >/dev/null real 0m1.589s user 0m1.588s sys 0m0.004s qwurx [shmem] ~> time python list.py in.txt >/dev/null real 0m2.204s user 0m2.188s sys 0m0.016s

        And your snake code does - according to the spec Given a line of numbers from a file, print every 2nd number starting from the back - get it right only for an even number of integers:

        qwurx [shmem] ~> python Python 2.7.9 (default, Jun 29 2016, 13:08:31) [GCC 4.9.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> line = "1 2 3 4" >>> line.split()[::-2] ['4', '2'] >>> line = "1 2 3 4 5" >>> line.split()[::-2] ['5', '3', '1'] >>> ^D

        So, what does every 2nd number starting from the back mean? If we start from the back and take every 2nd, the output for the even example should be ['3','1'], and for the odd example ['4','2']. If we count every 2nd from the beginning, the output ought to be ['4','2'] for both cases.

        Because you presented

        Example:

        1 22 3 -4 ==> -4 22

        almost all code examples in this thread assumed that every 2nd meant counting from the beginning, but outputting in reverse order. Tell the snake to do that, and compare again.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: printing every 2nd entry in a list backwards
by tobyink (Abbot) on May 19, 2017 at 15:47 UTC

    use v5.16; use List::Util qw/pairmap/; # this is in core if you have a recent Per +l my @list = qw( 1 22 3 -4 5 666 7 ); say pairmap { "$b " } reverse @list;

    Assuming you have the XS version of List::Util installed, this should blow away the grep/map solutions in terms of speed.

    UPDATE: Added the grep $i^=1, reverse solution, which is fast, but still loses to pairmap.

      Here's a mapslice

      mapslice => sub { my @x = @::list[ map {$_<<1} reverse 0..@::list>>1 ] + }

      which you might want to add.

      perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'

        Yeah, that's pretty fast.

        # pairmap - 4 wallclock secs ( 3.18 usr + 0.00 sys = 3.18 CPU) @ 17 +5.47/s (n=558) # grepbit - 3 wallclock secs ( 3.26 usr + 0.00 sys = 3.26 CPU) @ 11 +4.72/s (n=374) # mapslice - 3 wallclock secs ( 3.25 usr + 0.00 sys = 3.25 CPU) @ 1 +08.92/s (n=354) # mapidx - 3 wallclock secs ( 3.20 usr + 0.01 sys = 3.21 CPU) @ 76. +64/s (n=246) # mapmy - 3 wallclock secs ( 3.06 usr + 0.00 sys = 3.06 CPU) @ 73.8 +6/s (n=226) # grepmy - 3 wallclock secs ( 3.01 usr + 0.00 sys = 3.01 CPU) @ 69. +44/s (n=209) # grepstate - 3 wallclock secs ( 3.17 usr + 0.00 sys = 3.17 CPU) @ +34.07/s (n=108)

      tobyink: is_fastest, cool! You always have the newest toys! Do you have an opinion about why grep with state is so much slower than grep with my?


      The way forward always starts with a minimal test.

        Well, it necessitates using a block, and the block form of grep is a little slower than the expression form.

        grep { BLOCK } @list; # slow grep EXPRESSION, @list; # fast

        But it's not just that. Even if you make them both use the block form of grep, state comes out behind my. I guess they've just put a lot of work into optimizing how fast normal lexical variables work, and less work into state variables.

Re: printing every 2nd entry in a list backwards
by Discipulus (Monsignor) on May 19, 2017 at 12:31 UTC
    Welcome apprentice.. consider signing in

    If i have understood the question you can:

    UPDATE: ok i have misunderstood.. what i'm doing there is: given a file and a sequence of numbers, for this file print the reverse line order skipping even entries.

    use strict; use warnings; # shift the file from args my $file_path = shift @ARGV; # a simple switch my $sw = 1; # take indices in reverse order and skipping 2th 4th.. my @wanted_lines = grep {$sw++ and $sw % 2 == 0} reverse @ARGV; open my $fh, '<', $file_path or die "unable to open $file_path! $!"; # in list context eat all the file at once my @all_lines = <$fh>; # of those all lines print a slice: the slice is @wanted_lines # with all valuse lowered by one because indices of array start at 0 # lines starts a 1 print for @all_lines[map{$_-1}@wanted_lines]; # invoke like: # inverted_alternated.pl file_to_read.txt 1 2 3 4

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: printing every 2nd entry in a list backwards
by KurtZ (Monk) on May 19, 2017 at 12:01 UTC
    TIMTOWTDI

    You could use 2 pop in loop to extract the last 2 elements of an array.

    Similarly splice

    You can also count an index backwards.

Re: printing every 2nd entry in a list backwards
by Anonymous Monk on May 19, 2017 at 16:02 UTC
    What would a Perl6 idiomatic (and fast) solution look like?
      I don't have time now to test how fast it is, but these are two idiomatic ways of doing in Perl 6, both using a pointy block with two loop variables (only one of which is actually used).

      With the range operator and reverse:

      for (1..10).reverse -> $x, $y { say $y}
      And using the sequence operator which can build a reversed list:
      for 10...1 -> $x, $y {say $y }

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1190600]
Approved by Corion
Front-paged by 1nickt
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (8)
As of 2017-09-21 11:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    During the recent solar eclipse, I:









    Results (245 votes). Check out past polls.

    Notices?