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

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

The code I have works well, but I'm not entirely happy with it, so I would be glad to learn how it can be improved (shorter/clearer). This is a bit like a "programming exercise" I made up for myself, although I stumbled over this problem when writing a production program.

Problem: I have an even-sized array, with the property that the odd-numbered elements are never undef. This array should be processed left-to-right, two elements at a time, in a loop. The elements to be processed should be removed from the list. My solution goes like this:

# Array to be processed: @arr while(defined( ( my ($x,$y) = (shift(@arr), shift(@arr)) )[0])){ .... }
In this code, I combine the extraction of the first two elements from the list, with testing whether the list is already empty (which would set $x and $y to undef). Since the even-sized elements in the list are allowed to be undef, I test only $x.

However, I don't like the clumsy defined((my (...) ...)[0]). I would prefer a solution which looks as clean as the <c>each generator used to iterate over the elements of a hash. If I had this type of problem more frequently, I would perhaps write my own generator, but it would be overkill in this case. Maybe there is any other way I could improve my code?
-- 
Ronald Fischer <ynnor@mm.st>

Replies are listed 'Best First'.
Re: Processing pairs of values from even-sized array
by AnomalousMonk (Archbishop) on Jun 03, 2011 at 12:30 UTC

    Why not just:

    while(@arr) { my ($x,$y) = (shift(@arr), shift(@arr)); ... }

    Update: ... looks [like] the each generator ...
    Maybe (see List::MoreUtils):

    use List::MoreUtils qw(natatime); ;; my $it = natatime 2, @arr; while (my ($x, $y) = $it->()) { ... }
      Thanks a lot to you - and all the other ones who replied to my post! I was so focused on the idea having the declaration of my $x and $y inside the loop clause, that I didn't see this obvious solution!

      -- 
      Ronald Fischer <ynnor@mm.st>
Re: Processing pairs of values from even-sized array
by Fletch (Bishop) on Jun 03, 2011 at 12:41 UTC

    List::MoreUtils has pairwise and natatime, the former which works like a two-at-a-time map and the later makes an iterator that you kick in a while loop.

    Example: my @x = ('a' .. 'g'); my $it = natatime 3, @x; while (my @vals = $it->()) { print "@vals\n"; } This prints a b c d e f g

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

Re: Processing pairs of values from even-sized array
by johngg (Canon) on Jun 03, 2011 at 12:30 UTC

    How about using splice?

    knoppix@Microknoppix:~$ perl -E ' > @arr = ( 1 .. 6 ); > while ( @arr ) > { > ( $x, $y ) = splice @arr, 0, 2; > say qq{$x - $y}; > }' 1 - 2 3 - 4 5 - 6 knoppix@Microknoppix:~$

    I hope this is helpful.

    Cheers,

    JohnGG

Re: Processing pairs of values from even-sized array
by oxone (Friar) on Jun 03, 2011 at 12:40 UTC
    There's also a 'pairwise' function in List::MoreUtils, which might be worth a look.
Re: Processing pairs of values from even-sized array
by Bloodnok (Vicar) on Jun 03, 2011 at 12:32 UTC
    Hi Ronald ,

    Have you tried using each itself. as in something like ...

    my $array = { @array }; while (my ($k, $v) = each %$array) { . . }
    Or possibly simpler still, but I haven't yet worked out how to perform an in-line cast of a list to an array - while (my ($k, $v) = each %{@array}) {... generates errors coz the list is being evaluated in scalar, not list, context ... JIC you were wondering:-)

    A user level that continues to overstate my experience :-))
      my $array = { @array };

      This will 'randomize' (or 'hash-ize'?) the order of elements in the array, and  %{{@array}} likewise. Update: Oops:  each %{{@arr}} has another, entirely different, fatal (or should I say immortal?) flaw!

        I suppose that depends on whether the pairs have to pulled from the list in the same order in which they appear in the list ... which altho' implied, isn't, AFAICT, specified in/by the OP.

        Ooops, my mistake, it _is_ specified : 'This array should be processed left-to-right...'

        A user level that continues to overstate my experience :-))
      Have you tried using each itself.
      This doesn't work, because this would require $array being a hash reference.

      -- 
      Ronald Fischer <ynnor@mm.st>

        Interestingly, the 5.12+ each allows iteration over an array (and similarly for keys and values), but each 'key' is a succcessive index of the array, and each 'value' is the element at that index: not quite what you wanted in the OP.

        >perl -wMstrict -le "my @ra = qw(a b c d); ;; while (my ($i, $element) = each @ra) { print qq{ra $i is '$element'}; } " ra 0 is 'a' ra 1 is 'b' ra 2 is 'c' ra 3 is 'd'
Re: Processing pairs of values from even-sized array
by ciderpunx (Vicar) on Jun 03, 2011 at 15:56 UTC

    I'd normally use a slightly simpler version of JohnGG's splice approach. Something like

    my @arr = qw/1 2 3 4 5 6 7 8/; while(my ($x,$y) = splice @arr,0,2) { say "$x => $y"; }

    Something I've needed in the past was to get two arrays (odds and evens). Grep can do this. You could combine them into a hash thus:

    my @arr = qw/1 2 3 4 5 6 7 8/; my ($j,$i)=(0,0); my ($x,$y) = ([grep { ++$i % 2} @arr] , [grep {$j++ % 2} @arr]); my %hash; @hash{@{$x}}=@{$y}; for(keys %hash) { say "$_ => $hash{$_}" }
    Gives us:
    1 => 2 3 => 4 7 => 8 5 => 6
    Or you could iterate them (which is safer if some x may be undef)
    my @arr = qw/1 2 3 4 5 6 7 8/; push @arr, undef; push @arr, 9; my ($j,$i)=(0,0); my ($x,$y) = ([grep { ++$i % 2} @arr] , [grep {$j++ % 2} @arr]); $i=0; for(@{$x}){ say $_ ." => " . $y->[$i++] if($_); }
    Obviously that doesn't scale to big arrays. I'm sure List::MoreUtils is always the better approach.



      Something I've needed in the past was to get two arrays (odds and evens).

      You can also push onto two arrays using a ternary.

      knoppix@Microknoppix:~$ perl -E ' > push @{ $_ % 2 ? \ @odds : \ @evens }, $_ for 1 .. 8; > say qq{ Odds : @odds}; > say qq{Evens : @evens};' Odds : 1 3 5 7 Evens : 2 4 6 8 knoppix@Microknoppix:~$

      I hope this is of interest.

      Cheers,

      JohnGG

        List::MoreUtils also has  part to do similar things:

        >perl -wMstrict -le "use List::MoreUtils qw(part); use Data::Dumper; ;; my $i; my @parts = part { ++$i % 2 } 1 .. 8; ;; print Dumper \@parts; " $VAR1 = [ [ 2, 4, 6, 8 ], [ 1, 3, 5, 7 ] ];
Re: Processing pairs of values from even-sized array
by sundialsvc4 (Abbot) on Jun 03, 2011 at 16:52 UTC

    Pick a way that works, make it clear as a bell, add a comment and move on...