Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked

Switch the odd/even elements of an array

by jaredor (Priest)
on Mar 04, 2011 at 20:04 UTC ( #891508=perlquestion: print w/replies, xml ) Need Help??
jaredor has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks,

I'm looking for a perl idiom for switching adjacent array elements with each other, e.g., to go from

    1  2  3  4  5  6  7  8


    2  1  4  3  6  5  8  7

Every way that I've done this before has been rather grotty. My main interest is in having something that avoids using indexing; this is not only for personal preference, but also because my fellow users will also have to maintain what I give them and these users don't "do" indexing well. (Heck, *I* don't do indexing well.)

The main constraint is that order is important; these are Y,X pairs being transformed into X,Y pairs, and these pairs have to maintain relative order. Thus the naked reverse() call would not be appropriate. However, it is checked beforehand that there is an even number of elements in the array, so a clever use of reverse() might work.... Also there is no uniqueness for the Y values, so transforming to a hash and back has even more potential pitfalls than reverse().

And there is a second constraint: No CPAN. The corporate IT support for perl is "perl only" and is always a few years out-of-date. Thus natatime() is out of consideration :-(

A simple example of code that I currently write would be something like

@foo = (1 .. 8); @bar = map { $_%2 ? $foo[$_] : $foo[$_-2] } 1 .. @foo;

But I can't help feeling that there is some perlism using reverse() and/or splice() and/or shift() and/or ??? that would be more in line with the concept of a whole array function. My Google-fu let me down and I'm in enough of a pique to ask this question even though I know that most people would tell me I have an acceptable method at hand.

Apologies: The above code snippet is untested since I am writing this on a machine with no perl installed.

Your grateful novice

Replies are listed 'Best First'.
Re: Switch the odd/even elements of an array
by wind (Priest) on Mar 04, 2011 at 20:31 UTC
    use xor on the array indexes:
    my @foo = (1..8); my @bar = @foo[map {$_ ^ 1} (0..$#foo)]; print "@bar";
    2 1 4 3 6 5 8 7
    - Miller
Re: Switch the odd/even elements of an array
by BrowserUk (Pope) on Mar 04, 2011 at 21:14 UTC

    sub swap{ reverse( shift, shift ), @_ ? swap( @_ ) : (); } print swap( 1 .. 8 );; 2 1 4 3 6 5 8 7

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Can always count on you for fun code. To make it a little spookier, maybe inherit the @_:
      sub swap { reverse( shift, shift ), @_ ? &swap : (); }

        Expanding the solution to allow for reversing groups of multiple elements.

        use strict; use warnings; use feature qw{ say }; use List::Util qw{ min }; my @arr = ( 1 .. 9 ); say qq{@arr}; my @twos = reverseGroupsOf( 2, @arr ); say qq{@twos}; my @threes = reverseGroupsOf( 3, @arr ); say qq{@threes}; my @fours = reverseGroupsOf( 4, @arr ); say qq{@fours}; sub reverseGroupsOf { my $groupsOf = shift; my $rcReverse; $rcReverse = sub { reverse( map shift, 1 .. min scalar( @_, $groupsOf ), @_ ? &$rcReverse : (); }; &$rcReverse; }
        1 2 3 4 5 6 7 8 9 2 1 4 3 6 5 8 7 9 3 2 1 6 5 4 9 8 7 4 3 2 1 8 7 6 5 9

        I hope this is of interest.



Re: Switch the odd/even elements of an array
by Corion (Pope) on Mar 04, 2011 at 20:21 UTC

    I would basically do what you do, except that I'd create the list of indices first, and then rearrange the target array:

    my @foo = (1..8); my @indices = map { $_*2 +1 => $_*2 } 0..(@foo / 2)-1; @bar = @foo[ @indices ];
Re: Switch the odd/even elements of an array
by chromatic (Archbishop) on Mar 04, 2011 at 20:28 UTC

    Corion's approach is better than this (building and applying a single list of indexes is less work than repeated array mutations), but the reverse/splice dance you had in mind might look something like this:

    use Modern::Perl; use Test::More; main(); sub main { my @foo = (1 .. 8); my @bar = reverseswap( @foo ); is $bar[0], 2, 'swapped the first one'; is $bar[1], 1, '... with the second'; is $bar[2], 4, '... and so on'; is $bar[3], 3, '... and so on'; is $bar[4], 6, '... and so on'; is $bar[5], 5, '... and so on'; is $bar[6], 8, '... and so on'; is $bar[7], 7, '... and so on'; done_testing; } sub reverseswap { my @array; push @array, reverse splice @_, 0, 2 while @_; return @array; }
Re: Switch the odd/even elements of an array
by moritz (Cardinal) on Mar 04, 2011 at 22:52 UTC
Re: Switch the odd/even elements of an array (OT: no CPAN constraint)
by toolic (Bishop) on Mar 04, 2011 at 20:39 UTC
    And there is a second constraint: No CPAN. The corporate IT support for perl is "perl only" and is always a few years out-of-date. Thus natatime() is out of consideration :-(
    So... it's ok to copy some code from PerlMonks which is probably thrown together in a few minutes and is untested and undocumented...

    but it's not ok to copy code from a mature, well-documented, well-maintained, well-tested across multiple perl versions and OS's CPAN module which has an open license?

    You better make sure no one copied the contents of the natatime sub (which is pure Perl) in any of the answers provided.

        This is a wonderful link, thank you. I'm getting way more out of this question than I expected.

        Unfortunately, as I indicated in other replies, at my work the steel ball of perl destiny has fallen through the pachinko machine of corporate IT obstacles to land in the APATHY slot.

        If there is one thing I have learned when I was a programmer, it was that life is much easier when external dependencies are minimized. bellaire has a nice essay (found via your link), but I think the reasons given for why one doesn't want an external dependency is a false dichotomy. Here's a real reason: Support is a non-trivial concern with module/library use. All that happy hoo-ha about generality and testing becomes secondary when the module isn't there. If you can't think of at least three reasons why a module wouldn't be where you put it, then you either aren't sufficiently imaginative or your corporate IT isn't sufficiently stultifying.

      Sorry, I should have been more precise: The lack of CPAN is a fact at my place of employ, not a policy.

      The scripts I write are just for myself to get my work done. The maintenance problem happens when a friend asks to use one of my scripts. I could tell them to put my personal bin directory in the front of their PATH, but my conscience compels me instead to give them a script that works without any dependencies upon my environment. Why, hello perl5.8 with no CPAN. (Now when I get laid off, my friends' scripts will still keep on working :-)

      This was a request for an idiom, so I'm not that concerned about copying code and corresponding quality concerns.

      That being said, there are lot of excellent answers here.

Re: Switch the odd/even elements of an array
by nikosv (Chaplain) on Mar 04, 2011 at 20:20 UTC
Re: Switch the odd/even elements of an array
by Marshall (Abbot) on Mar 05, 2011 at 15:43 UTC
    Another solution. The input array is known to have an even number of elements. Just take pairs off of the top of @input, swap 'em, push 'em to a temp array, then return that array when done. No need for recursion, indexing or anything "tricky".
    #!/usr/bin/perl -w use strict; my @input = (1,2,3,4,5,6,7,8); print swap_pairs(@input); # prints: 21436587 sub swap_pairs { my @swapped_pairs; while ( my ($x,$y) = splice(@_,0,2) ) { push @swapped_pairs,($y,$x); } return @swapped_pairs; }
Re: Switch the odd/even elements of an array
by JavaFan (Canon) on Mar 05, 2011 at 23:59 UTC
    my @bar = @foo [map {$_ + ($_ % 2) * -2 + 1} 0 .. $#foo]; # Destroys the array. Doesn't work in situ. my @bar; push @bar, reverse splice @foo, 0, 2 while @foo; # Doesn't work in situ. use List::MoreUtils ':all'; my (@bar, @t); my $i = natatime, 2, @foo; push @bar, reverse @t while @t = $i->(); # Doesn't work if there's an undefined value on an even position. my @bar = do {my $t; map {defined $t ? do {my $x = $t; undef $t; ($_, +$x)} : do {$t = $_; ()}} @foo};
Re: Switch the odd/even elements of an array
by jdporter (Canon) on Mar 07, 2011 at 19:11 UTC
    # a recursive approach, similar to BrowserUK's sub swap1 { my @t = ( pop, pop ); ( @_ ? &swap1 : (), @t ) } # an iterative approach sub swap2 { my @t; unshift @t, ( pop, pop ) while @_; @t }

    I've tried to avoid any reference to the index whatsoever, and also reverse, which I think is too "obvious".
    BrowserUK's solution is superior in that it avoids temporary variables as well.

    I reckon we are the only monastery ever to have a dungeon stuffed with 16,000 zombies.
Re: Switch the odd/even elements of an array
by jdrago999 (Pilgrim) on Mar 06, 2011 at 19:33 UTC
    my %numbers = 1..8; my @sorted = map { $numbers{$_} => $_ } sort keys %numbers; print join ", ", @sorted;

    Prints 2, 1, 4, 3, 6, 5, 8, 7

    We rely on Perl's implicit conversion of lists to hashes, and use sort to get them back out of the hash in the desired order.

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (7)
As of 2018-06-19 17:08 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (114 votes). Check out past polls.