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

Let us say I have a scalar with a comma delimited list and I use split to form an array. That is not so hard, but what if I want all the even values of the array to go into one array and all the odds into another?
$scalar = '1,2,3,4,5,6,7,8'; @array = split(/\,/,$scalar);
How could I get the array containing 1,3,5,7 and the one containing 2,4,6,8?

The actual application I am working on has text instead of numbers so it gets mildly harder, but that is my job...

thanks a bunch


jcpunk
all code is tested, and doesn't work so there :p (varient on common PM sig for my own ammusment)

Replies are listed 'Best First'.
Re: split every other value
by Zaxo (Archbishop) on Aug 07, 2004 at 00:10 UTC

    Here's an evil trick to do it,

    my %hash; %hash = split /,/, $scalar; my @evens = keys %hash; my @odds = values %hash;
    That does not preserve order, but each value is the element following its key.

    After Compline,
    Zaxo

      That's fine until you have two identical elements in any of the even fields.

      Makeshifts last the longest.

Re: split every other value
by Aristotle (Chancellor) on Aug 07, 2004 at 00:08 UTC

    That begs a question of semantics. Do you mean the even vs odd values?

    Then the common idiom would be something like

    my (@even, @odd); push @{ $_ % 2 ? \@odd : \@even }, $_ for split /,/, $text;

    Here, you loop over all the elements, giving push either @odd or @even to push the element onto, depending on whether the element is odd or even. The awkward @{ ... } construct is necessary because push demands that its first argument have a @ sigil in front, no matter what.

    Or, do you mean the even vs odd positions in the list?

    Then you'd use something like

    my (@even, @odd); my $i = 0; push @{ $i++ % 2 ? \@odd : \@even }, $_ for split /,/, $text;

    Here, you increase a position counter at each iteration, and make the decision depending on its even/oddness.

    Update: see Re^6: split every other value about the following code and Re^7: split every other value for my reply with a correct derivative.

    Another option in this case is something like

    my @field = split /,/, $text; my (@even, @odd) = @field[ map { $_, $_ + @field / 2 } 0 .. ( @field / 2 ) - 1 ];

    Makeshifts last the longest.

      That won't work for text, however, which is what the problem supposedly really is. jcpunk wants to split on odd/even array indexes, not on the odd/evenness of the array element.

      Update: Oh good, you have a second version which divides the stuff up on array index.

        Read the code again. That's exactly what it does.

        Makeshifts last the longest.

Re: split every other value
by Prior Nacre V (Hermit) on Aug 07, 2004 at 00:16 UTC

    This is one way:

    my $ra_all = [ [], [] ]; map { push @{$ra_all->[$_ % 2]}, $_ } @array; my @evens = @{$ra_all->[0]}; my @odds = @{$ra_all->[1]};

    Regards,

    PN5

      And even faster with optimisation:

      my (@evens, @odds); my $ra_all = [ \@evens, \@odds ]; map { push @{$ra_all->[$_ % 2]}, $_ } split /,/, $scalar;

      Regards,

      PN5

        I don't understand the point of the third arrayref. I also find it easier on the eyes with a foreach, but to each his own.

        my ( @evens, @odds ); my @all = ( \@even, \@odd ); push @{ $all[ $_ % 2 ] }, $_ for split /,/, $scalar;

        Brevity, but don't do this at home: you can combine the first two lines into

        my @all = \my ( @evens, @odds );

        Makeshifts last the longest.

Re: split every other value
by beable (Friar) on Aug 07, 2004 at 00:15 UTC
    #!/usr/bin/perl use strict; use warnings; my $scalar = '1,2,3,4,5,6,7,8'; my @array = split(/\,/,$scalar); print "array = @array\n"; # do the odd/even split my @odd = (); my @even = (); while (@array) { my $odd = shift @array; my $even = shift @array; if (defined $odd) { push @odd, $odd; } if (defined $even) { push @even, $even; } } print "odd = @odd, even = @even\n"; __END__

      You got your odd/even mixed up. The first element is at index 0, which is even. Also, enter splice and statement modifiers.

      while( @array ) { my ( $even, $odd ) = splice @array, 0, 2; push @odd, $odd if defined $odd; push @even, $even if defined $even; }

      It is worth noting that this would be buggy if the list could possibly have undefs in it (not the case here; but I'd put a comment to that effect in the code).

      Obscure trickery for the fun of reducing it further, but don't do this at home:

      ( $even[ @even ], $odd[ @odd ] ) = splice @array, 0, 2 while @array;

      Makeshifts last the longest.

        I shouldn't have used the names "@odd" and "@even", seeing as how we're supposed to be dealing with text. So here's a program which doesn't use those terribly misleading names, and deals with undefs.

        #!/usr/bin/perl use strict; use warnings; my @array = map{chr} ('32' .. '126'); push @array, undef, undef; unshift @array, undef, undef, undef; # split elements into two arrays my (@arr0, @arr1); while (@array > 1) { my ($el0, $el1) = splice @array, 0, 2; push @arr0, $el0; push @arr1, $el1; } # get the last element if there is one if(@array) { push @arr0, shift @array; } # check that all elements were gotten die "still elements left" if (@array); print "arr0 = @arr0\narr1 = @arr1\n"; __END__
Re: split every other value
by johnnywang (Priest) on Aug 07, 2004 at 02:58 UTC
    Here's another, using regex. Frankly I'm a little shaky on why it actually works:
    my $a = "1,2,3,4,5,6,7,8"; my @even = ($a =~ /(.+?),.+?,?/g); #prints 1,3,5,7 my @old = ($a =~ /.+?,(.+?),?/g); # prints 2,4,6,8
Re: split every other value
by sleepingsquirrel (Hermit) on Aug 07, 2004 at 00:18 UTC
    In the spirit of TMTOWTDI...
    $list = '1,2,3,4,5,6,7,8'; @odds = grep $_ % 2, split(/,/, $list); @evens = grep not($_ % 2), split(/,/, $list);
    Update: just read the part about text not numbers, so this might be more appropriate...
    $list = 'a,b,c,d,e,f,g,h'; $i=0; @pos1 = grep not($i++ % 2), split(/,/, $list); $i=0; @pos2 = grep $i++ % 2, split(/,/, $list);


    -- All code is 100% tested and functional unless otherwise noted.

      That requires iterating over the list twice, though.

      Makeshifts last the longest.

Re: split every other value
by shemp (Deacon) on Aug 07, 2004 at 01:58 UTC
    how about just:
    my $scalar = '1,2,3,4,5,6,7,8'; print "$scalar\n"; my @evens; while ( $scalar =~ s/^([^,]),[^,](,|$)// ) { push @evens, $1; } print "@evens\n";

      You don't need to use a substitution if you anchor the pattern using \G. And why not capture the odd elements as well?

      my ( @even, @odd ); while ( $text =~ /\G ( [^,]+ ) (?: , ( [^,]+ ) )? (?: , | \z ) /gx ) +{ push @even, $1; push @odd, $2 if defined $2; }

      Makeshifts last the longest.

Re: split every other value
by BrowserUk (Pope) on Aug 07, 2004 at 06:10 UTC

    my $s = join',', 1..10; my( @odds, @evens ); m[([^,]+),([^,]+)] and push @odds, $1 and push @evens, $2 for split /([^,]+,[^,]+)(?:,|$)/, $s; print "@odds\n@evens";; 1 3 5 7 9 2 4 6 8 10

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon

      You didn't test that with an odd number of elements in $s, did you? :-)

      Makeshifts last the longest.

        You right. Here's a better way that resurrects and old snippet with (I think), improvements:

        sub mapn (&$@) { my( $code, $n, ) = ( shift, shift ); map { $code->( @_[ $_ .. $_ + $n -1 ] ); } map $n * $_, 0 .. ( $#_ / $n ); } my $s = join',', 1 .. 11; my( @odds, @evens ); mapn{ push @odds, shift||(); push @evens, shift||(); } 2, split ',', $s; print "@odds\n@evens";; 1 3 5 7 9 11 2 4 6 8 10

        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: split every other value
by DrHyde (Prior) on Aug 09, 2004 at 08:32 UTC
    Assuming you want to seperate the odd and even array indices, then you want the inverse of a zip() function - and lo and behold, Language::Functional has unzip()!

    A zip function takes two lists as its input, and interleaves them, so given the lists:

    qw(ant bat camel)
    and
    qw(aardvark bird cow)
    it would spit out a list
    qw(ant aardvark bat bird camel cow)
    .

    and unzip() does the exact opposite. Language::Functional is a very useful module indeed, despite the very low version number.

Re: split every other value
by qq (Hermit) on Aug 07, 2004 at 13:30 UTC

    nothing fancy:

    #!/usr/bin/perl my @array = ( a .. x, undef ); print "@array\n"; my (@a,@b); for ( 0 .. $#array ) { if ( $_ % 2 ) { push @b, $array[$_]; } else { push @a, $array[$_]; } } print "a: '@a'\n"; print "b: '@b'\n";

    qq

Re: split every other value
by ChrisJ-UK (Novice) on Aug 08, 2004 at 20:36 UTC

    Not glamorous but sturdy and works for text just as well as numbers:

    #!/usr/bin/perl use strict; my $scalar = '1,2,3,4,5,6,7,8'; my @array = split(/\,/,$scalar); my $flag = 1; my ($element, @odds, @evens, $odd, $even); foreach $element(@array) { if ($flag==1) { push (@odds, $element); $flag=2; } else { push (@evens, $element); $flag=1; } } print "Odd elements: "; foreach $odd(@odds) { print "$odd "; } print "\nEven elements: "; foreach $even(@evens) { print "$even "; }

    Hope this of some help.

    Chris